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:
- Extending the GraphQL API
- Extending the REST API
- Using codegen with Nextjs and React Query
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.ts23import rest from './rest'45// ...67endpoints: rest,89// ...
Now we'll edit the rest/index.ts
1// ./rest/index.ts23import { Endpoint } from 'payload/config'45const rest: Endpoint[] = [6 // our endpoints will go here7]89export default rest10
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.ts23import { Endpoint } from 'payload/config'4import getLatestPost from '../handlers/getLatestPost'56const getLatestPostEndpoint: Endpoint = {7 path: '/getLatestPost',8 method: 'get', // 'get' or 'post'9 handler: getLatestPost,10}1112export default getLatestPostEndpoint
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.ts23import { Endpoint } from 'payload/config'45const getLatestPost: Endpoint['handler'] = async ({ user, ...req }, res, next) => {6 const payload = req.payload78 const {9 docs: [latestPost],10 } = await payload.find({11 collection: 'posts',12 sort: '-createdAt',13 })1415 res.status(200).send(latestPost)16}1718export default getLatestPost19
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.ts234// add this configuration AFTER payload is initialised56 const router = express.Router()7 router.use(payload.authenticate)89 router.get('/', (req, res) => {10 if (req.user) {11 return res.send(`Authenticated successfully as ${req.user.email}.`)12 }1314 return res.send('Not authenticated')15 })1617 app.use('/some-route-here', router)18
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 file2import type { User } from 'payload/dist/auth'3import type { Request } from 'express'45// attach the User to the request with a custom interface67interface RequestWithUser extends Request {8 user: User9}1011// ...1213// type the req with our new interface1415router.get('/', (req: RequestWithUser, res) => {
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.