Extend the REST API in Payload with Express Cover

How to extend the REST API in Payload with Express

Payload comes out of the box with a REST API ready to go and the admin interface actually uses the same API as exposed to you to handle all the operations, so it's only natural that we may want to extend the power of Payload to add more functionality and we can do that not only through Payload but directly via express as well.

This article is part of a series of working with the Payload APIs:

Setup

Directory

We'll start by clarifying the working directory

  • rest/

    • endpoints/
    • handlers/
    • index.ts
  • payload.config.ts

Configuration

We don't need to install anything extra for this step, however we do want to configure Payload to accept our new API routes as stated by the documentation.

1// payload.config.ts
2
3import rest from './rest'
4
5// ...
6
7endpoints: rest,
8
9// ...
typescript

Now we'll edit the rest/index.ts

1// ./rest/index.ts
2
3import { Endpoint } from 'payload/config'
4
5const rest: Endpoint[] = [
6 // our endpoints will go here
7]
8
9export default rest
10
typescript

Now let's add our first endpoint, we're going to add just one GET handler that allows us to fetch the latest post.

1// rest/endpoints/getLatestPost.ts
2
3import { Endpoint } from 'payload/config'
4import getLatestPost from '../handlers/getLatestPost'
5
6const getLatestPostEndpoint: Endpoint = {
7 path: '/getLatestPost',
8 method: 'get', // 'get' or 'post'
9 handler: getLatestPost,
10}
11
12export default getLatestPostEndpoint
typescript

So what this configuration means is that our new endpoint will be available at /api/getLatestPost and it accepts a GET method and runs the getLatestPost handler function when called.

You can accept other methods too by the way, but the majority of people will be using a POST or a GET so we simplified our example.

The last step is to add our handler:

1// rest/handlers/getLatestPost.ts
2
3import { Endpoint } from 'payload/config'
4
5const getLatestPost: Endpoint['handler'] = async ({ user, ...req }, res, next) => {
6 const payload = req.payload
7
8 const {
9 docs: [latestPost],
10 } = await payload.find({
11 collection: 'posts',
12 sort: '-createdAt',
13 })
14
15 res.status(200).send(latestPost)
16}
17
18export default getLatestPost
19
typescript

With the handler done, we can now make a GET request to http://localhost:3000/api/getLatestPost to get your data!

Additional logic

So now you can add your needed complexity in this handler, and as per Payload middleware you will find the authenticated user in req.user if the request is authenticated and you can pass that user into local API to take advantage of your permission hooks configured in the collections.

Don't import payload, use the one that's passed as an argument into the function.

Remember that by default the local API bypasses all access checks.

Extending the API via Express

The documentation covers this really well but since you're here we will expand on it too and because Payload doesn't get in your way you can also bypass it entirely if you want to add your own endpoints via the Express server and the best part is you can still use Payload's middleware functions if you still want to authenticate users or do any data fetching.

If you know Express then this should be entirely familiar to you

1// server.ts
2
3
4// add this configuration AFTER payload is initialised
5
6 const router = express.Router()
7 router.use(payload.authenticate)
8
9 router.get('/', (req, res) => {
10 if (req.user) {
11 return res.send(`Authenticated successfully as ${req.user.email}.`)
12 }
13
14 return res.send('Not authenticated')
15 })
16
17 app.use('/some-route-here', router)
18
typescript

This route is now available at http://localhost:3000/some-route-here and yes we copied their example =)

However, we will show you how to add additional types to the request as well so that you don't get a typescript any if you're using the authentication middleware.

1// import the types at the top of your server.ts file
2import type { User } from 'payload/dist/auth'
3import type { Request } from 'express'
4
5// attach the User to the request with a custom interface
6
7interface RequestWithUser extends Request {
8 user: User
9}
10
11// ...
12
13// type the req with our new interface
14
15router.get('/', (req: RequestWithUser, res) => {
typescript

That's it and you can extend your custom interface with other properties should you need it.

As usual the full example repository is here, and you can follow us on Twitter if you want to keep up with our releases and up next we will work on the GraphQL extension tutorial.

If you have any questions or topics you want us to cover just send us a message.