Does NestJS Work With Payload CMS?
You can use NestJS with Payload CMS, but they're better as separate services rather than tightly integrated—NestJS handles your API/business logic while Payload runs as a headless CMS.
Quick Facts
How NestJS Works With Payload CMS
NestJS and Payload CMS can coexist in the same project, but the typical architecture treats them as distinct services communicating via REST or GraphQL APIs. Payload is a full-featured headless CMS with its own Express server, authentication, and database layer, while NestJS excels at building business logic and microservices. You have two main approaches: run them as separate services (recommended) where NestJS consumes Payload's API, or embed Payload as a plugin within a NestJS monorepo using Payload's custom server configuration. The latter requires careful dependency management since both frameworks have strong opinions about routing, middleware, and database connections. Most developers prefer the decoupled approach because it gives you independence—you can scale, update, or replace either service without affecting the other. If you need tight coupling (like accessing Payload's database directly from NestJS), you'll need to configure shared database connections and handle ORM conflicts between Payload's MongoDB integration and your NestJS TypeORM/Prisma setup.
Best Use Cases
NestJS Service Consuming Payload CMS API
npm install @nestjs/common @nestjs/core axiosimport { Injectable } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class PayloadService {
private readonly payloadUrl = 'http://localhost:3000/api';
private readonly apiKey = process.env.PAYLOAD_API_KEY;
async getPages() {
const response = await axios.get(`${this.payloadUrl}/pages`, {
headers: { Authorization: `Bearer ${this.apiKey}` },
});
return response.data;
}
async createArticle(data: any) {
const response = await axios.post(
`${this.payloadUrl}/articles`,
data,
{ headers: { Authorization: `Bearer ${this.apiKey}` } }
);
return response.data;
}
}
@Injectable()
export class ContentController {
constructor(private payloadService: PayloadService) {}
async getPublishedContent() {
return this.payloadService.getPages();
}
}Known Issues & Gotchas
Both frameworks want to own the Express/HTTP server if you try to run them together in one process
Fix: Keep them as separate services. Run Payload on port 3000 and NestJS on port 3001, communicate via HTTP/REST or GraphQL. This is the recommended architecture.
Payload's authentication (JWT tokens) won't automatically work with NestJS guards unless you implement custom validators
Fix: Create a custom NestJS authentication strategy that validates Payload JWTs, or use separate auth systems and sync via API calls.
TypeScript type generation from Payload isn't automatic in NestJS—you need to manually maintain DTOs or use Payload's generated types
Fix: Use Payload's TypeScript collection type exports and import them into NestJS for end-to-end type safety across services.
Database transaction coordination is difficult when services use different databases or connection pools
Fix: Design your system to avoid distributed transactions. Use eventual consistency patterns and webhooks for cross-service data synchronization.
Alternatives
- •Strapi + NestJS: Strapi is more lightweight than Payload and integrates more naturally with custom Node backends
- •Sanity + NestJS: Sanity is cloud-hosted and pairs well with NestJS through their GraphQL API, reducing operational overhead
- •NestJS + Directus: Directus is simpler and more API-first, making service decoupling cleaner
Resources
Related Compatibility Guides
Explore more compatibility guides