In NestJS apps, it's common to use global route prefixes and versioning like /api/v1. But sometimes you need a public-facing endpoint — such as a payment gateway callback — that must live outside those constraints.
Here’s how to expose routes like /payment/callback without the global /api/v1 prefix.
🔧 Problem
You're using global prefixing and versioning like this:
main.ts:
app.setGlobalPrefix('api'); app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1', }); ✅ Resulting in routes like:
/api/v1/auth/login
/api/v1/user/profile
❌ But now you need /payment/callback to be exposed at:
http://localhost:3000/payment/callback
Instead of:
http://localhost:3000/api/v1/payment/callback
✅ Solution Overview
You need to do two things:
⛔ Exclude the route from the global prefix
⛔ Mark the route as version-neutral
- Exclude the Route from Global Prefix Update main.ts:
import { RequestMethod } from '@nestjs/common'; app.setGlobalPrefix('api', { exclude: [{ path: 'payment/callback', method: RequestMethod.GET }], }); This tells NestJS to ignore prefixing for GET /payment/callback
🚫 No leading slash in path (correct: payment/callback)
- Mark Route as Version-Neutral Update your controller:
import { Controller, Get, Version, VERSION_NEUTRAL } from '@nestjs/common'; @Controller('payment') export class PaymentController { @Get('callback') @Version(VERSION_NEUTRAL) callback() { return 'Payment callback hit!'; } } ✅ VERSION_NEUTRAL tells NestJS: “do not apply any version prefix”
🧪 Final Result
http://localhost:3000/payment/callback ✅
http://localhost:3000/api/v1/payment/callback ❌
All other routes still work normally under /api/v1.
🧵 Conclusion
NestJS makes it easy to maintain clean API versioning — but when you need an exception like a public payment callback, it only takes
two steps:
- Exclude the path in setGlobalPrefix()
- Add @version(VERSION_NEUTRAL) to the controller
Top comments (0)