A production-ready Next.js 16+ API backend template with MongoDB integration, dynamic OpenAPI generation, and automatic type-safe documentation.
- Functional Architecture: Pure functions with Zod schemas - no classes or decorators
- Type-Safe Validation: Zod schemas provide runtime validation and type inference
- Automatic OpenAPI Generation: Uses
zod-to-openapifor spec generation - Co-located Schemas: Route-centric organization with schemas next to handlers
- Zero Configuration: Works out of the box with automatic route discovery
- Live Documentation: Interactive Swagger UI at
/api/docs - SDK Generation: Auto-generated React Query hooks with Orval
- Mongoose ODM: Full MongoDB integration with schema validation
- Connection Caching: Optimized for serverless environments
- Middleware Pattern:
withDatabasewrapper for seamless DB connections - Model Organization: Clean separation of concerns with organized models
- Hot Reload: Watch mode for automatic OpenAPI regeneration
- Type Safety: Full TypeScript support with strict type checking
- Route-Centric: Co-located schemas and endpoints for better maintainability
- Modern Stack: Next.js 16+ App Router with ES modules
- Node.js 18+
- MongoDB Atlas account or local MongoDB instance
- npm or yarn
# Clone the template
git clone https://github.com/YousifAbozid/template-nextjs-backend.git
cd template-nextjs-backend
# Install dependencies
npm install
# Setup environment
cp .env.example .env
# Edit .env with your MongoDB connection string
# Generate API documentation and SDK
npm run api:generate
npm run api:sdk
# Start development server
npm run dev# .env
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/database
API_TITLE="Your API Title"
API_VERSION="1.0.0"
API_DESCRIPTION="Your API Description"app/
βββ api/ # π’ API Routes (You modify these)
β βββ users/
β β βββ route.ts # API endpoints (GET, POST, etc.)
β β βββ schema.ts # Zod schemas with OpenAPI metadata
β β βββ openapi.ts # OpenAPI route definitions
β βββ health/ # Health check endpoint
β βββ docs/ # Swagger UI documentation
β βββ openapi.json/ # OpenAPI JSON endpoint
βββ lib/ # Shared utilities and business logic
β βββ api/
β βββ database/ # π’ Database connection utilities
β βββ middleware/ # π’ Route handler wrappers (withDatabase, etc.)
β βββ models/ # π’ Mongoose models
β βββ openapi/ # π’ OpenAPI registry and helpers
β βββ config.ts # π‘ API configuration
β βββ sdk-mutator.ts # π‘ SDK fetch configuration
βββ layout.tsx # Root layout
βββ page.tsx # Landing page
proxy.ts # π’ Next.js 16+ global proxy (CORS)
sdk/
βββ index.ts # π΄ Auto-generated SDK (don't edit)
openapi.json # π΄ Auto-generated OpenAPI spec (don't edit)
Legend: π’ Safe to modify | π‘ Modify carefully | π΄ Auto-generated (don't touch)
This project uses two types of middleware:
-
Global Proxy (
proxy.tsat root) - New in Next.js 16+- Next.js edge proxy function that runs before all requests
- Named
proxy.tsand exportsproxy()function (changed frommiddleware.tsin v16) - Handles CORS for all API routes
- Next.js Middleware Docs
-
Route Handler Wrappers (
app/lib/api/middleware/)- Function wrappers for individual route handlers
- Examples:
withDatabase(ensures DB connection) - Used by wrapping your route handlers Legend: π’ Safe to modify | π‘ Modify carefully | π΄ Auto-generated (don't touch)
mkdir app/api/products
touch app/api/products/route.ts app/api/products/schema.ts app/api/products/openapi.tsimport { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
extendZodWithOpenApi(z);
export const ProductSchema = z
.object({
_id: z.string().openapi({ description: 'Product ID' }),
name: z
.string()
.min(1)
.openapi({ description: 'Product name', example: 'Laptop' }),
price: z
.number()
.min(0)
.openapi({ description: 'Product price', example: 999.99 }),
category: z
.string()
.openapi({ description: 'Category', example: 'Electronics' }),
description: z.string().optional().openapi({ description: 'Description' }),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
})
.openapi('Product');
export const CreateProductRequestSchema = z
.object({
name: z.string().min(1).openapi({ description: 'Product name' }),
price: z.number().min(0).openapi({ description: 'Product price' }),
category: z.string().openapi({ description: 'Category' }),
description: z.string().optional(),
})
.openapi('CreateProductRequest');
export const ProductListResponseSchema = z
.object({
success: z.literal(true),
data: z.array(ProductSchema),
count: z.number(),
})
.openapi('ProductListResponse');
export const ProductResponseSchema = z
.object({
success: z.literal(true),
data: ProductSchema,
message: z.string().optional(),
})
.openapi('ProductResponse');
export type Product = z.infer<typeof ProductSchema>;
export type CreateProductRequest = z.infer<typeof CreateProductRequestSchema>;import { registry, createRouteConfig } from '@/lib/api/openapi';
import {
CreateProductRequestSchema,
ProductListResponseSchema,
ProductResponseSchema,
} from './schema';
registry.registerPath(
createRouteConfig({
method: 'get',
path: '/api/products',
tags: ['Products'],
summary: 'Get all products',
responses: {
200: {
description: 'Products retrieved successfully',
content: {
'application/json': { schema: ProductListResponseSchema },
},
},
},
})
);
registry.registerPath(
createRouteConfig({
method: 'post',
path: '/api/products',
tags: ['Products'],
summary: 'Create a new product',
request: {
body: {
content: {
'application/json': { schema: CreateProductRequestSchema },
},
},
},
responses: {
201: {
description: 'Product created successfully',
content: {
'application/json': { schema: ProductResponseSchema },
},
},
},
})
);import { NextRequest, NextResponse } from 'next/server';
import { withDatabase } from '@/lib/api/middleware';
import { Product } from '@/lib/api/models';
import { CreateProductRequestSchema } from './schema';
import './openapi'; // Import to register routes
export const GET = withDatabase(async () => {
const products = await Product.find().sort({ createdAt: -1 });
return NextResponse.json({
success: true,
data: products,
count: products.length,
});
});
export const POST = withDatabase(async (req: NextRequest) => {
const body = await req.json();
// Validate with Zod
const validationResult = CreateProductRequestSchema.safeParse(body);
if (!validationResult.success) {
return NextResponse.json(
{ success: false, error: validationResult.error.issues[0].message },
{ status: 400 }
);
}
const product = new Product(validationResult.data);
const savedProduct = await product.save();
return NextResponse.json(
{
success: true,
data: savedProduct,
message: 'Product created successfully',
},
{ status: 201 }
);
});import mongoose, { Schema, Document } from 'mongoose';
export interface IProduct extends Document {
name: string;
price: number;
category: string;
description?: string;
createdAt: Date;
updatedAt: Date;
}
const ProductSchema: Schema<IProduct> = new Schema(
{
name: { type: String, required: true, trim: true },
price: { type: Number, required: true, min: 0 },
category: { type: String, required: true, trim: true },
description: { type: String, trim: true },
},
{ timestamps: true }
);
export const Product =
mongoose.models.Product || mongoose.model<IProduct>('Product', ProductSchema);npm run api:generate # Generate OpenAPI spec
npm run api:sdk # Generate React Query SDKResult: Your new route automatically appears in:
- OpenAPI spec at
/api/openapi.json - Interactive docs at
/api/docs - Generated SDK with React Query hooks in
sdk/index.ts
npm run dev # Start development server
npm run build # Build for production (includes OpenAPI + SDK generation)
npm run start # Start production server
npm run api:generate # Generate OpenAPI spec from route definitions
npm run api:sdk # Generate React Query SDK from OpenAPI spec
npm run api:watch # Watch mode for OpenAPI + SDK regeneration
npm run api:dev # Start dev server + OpenAPI/SDK watching
npm run type-check # TypeScript type checking
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier formatting
npm run format:check # Check formatting without writing
npm run fix-all # Run lint:fix + format
npm run test # Run all checks (format:check, lint, type-check)Once running, access your API documentation:
- Interactive Docs: http://localhost:3000/api/docs
- OpenAPI Spec: http://localhost:3000/api/openapi.json
- Health Check: http://localhost:3000/api/health
The system automatically:
- Scans OpenAPI Files: Finds all
app/api/**/openapi.tsfiles - Imports Route Definitions: Loads registry entries from each file
- Generates OpenAPI Spec: Creates
openapi.jsonin project root - Generates SDK: Uses Orval to create React Query hooks in
sdk/index.ts - Type Safety: Full TypeScript types inferred from Zod schemas
openapi.json- OpenAPI 3.0 specification (project root)sdk/index.ts- Type-safe React Query hooks for API consumption
- Framework: Next.js 16+ (App Router)
- Language: TypeScript with ES Modules
- Database: MongoDB with Mongoose
- Validation: Zod - TypeScript-first schema validation
- OpenAPI: zod-to-openapi - Generate OpenAPI from Zod schemas
- SDK Generation: Orval - Generate React Query hooks from OpenAPI
- Documentation: Swagger UI - Interactive API documentation
- Code Quality: ESLint + Prettier + TypeScript
- Development: Chokidar file watching + Concurrently
- Development: Chokidar file watching + Concurrently
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel --prod# Build image
docker build -t nextjs-backend .
# Run container
docker run -p 3000:3000 nextjs-backend# Build for production
npm run build
# Start production server
npm start- Development Guide - Comprehensive development documentation
- Middleware Guide - Complete guide to global vs route-specific middleware
- SDK Usage Examples - How to use the auto-generated SDK
- GitHub Copilot Instructions - AI coding assistant setup
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Next.js team for the amazing framework
- Mongoose for MongoDB integration
- OpenAPI specification contributors
- Swagger for API documentation tools
Built with β€οΈ by Yousif Abozid