A production-ready Next.js 16+ full-stack template with TypeScript, MongoDB, Tailwind CSS v4, and comprehensive tooling. Features auto-generated OpenAPI documentation, type-safe API client, React Query integration, dark mode support, custom UI components with Framer Motion, form validation with Zod, and complete developer experience.
- Zod-Based Schemas: Use Zod for validation with built-in OpenAPI metadata
- Functional Registry: Declarative route registration with
@asteasolutions/zod-to-openapi - Type-Safe SDK: Auto-generated React Query hooks with Orval
- Zero Decorators: Clean, functional patterns without class decorators
- Live Documentation: Interactive Swagger UI at
/api/docs - Full Type Safety: End-to-end TypeScript with Zod inference
- 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
- Modern UI Library: Comprehensive component system with Tailwind CSS v4
- Dark Mode Support: Built-in theme switching with CSS custom properties
- Framer Motion: Smooth animations and page transitions
- Form Validation: Type-safe forms with react-hook-form + Zod
- State Management: React Query for server state, React hooks for client state
- Hot Reload: Watch mode for automatic OpenAPI regeneration
- Type Safety: Full TypeScript support with strict type checking
- Route-Centric: Co-located types 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-ts.git
cd template-nextjs-ts
# 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 for validation + OpenAPI
β β βββ openapi.ts # OpenAPI route registration
β βββ health/ # Health check endpoint
β βββ docs/ # Swagger UI documentation
β βββ openapi.json/ # OpenAPI specification endpoint
βββ components/ # π’ React components
β βββ ui/ # Base UI components (Button, Card, etc.)
β βββ shared/ # Shared business components
βββ context/ # π’ React context providers
β βββ Providers.tsx # App-level providers
β βββ ThemeProvider.tsx # Dark/light mode
β βββ ToastContext.tsx # Toast notifications
βββ lib/ # Shared utilities and business logic
β βββ api/
β β βββ database/ # π’ Database connection utilities
β β βββ middleware/ # π’ Custom middleware
β β βββ models/ # π’ Mongoose models
β β βββ openapi/ # π’ OpenAPI registry and helpers
β β βββ config.ts # π‘ API configuration
β β βββ sdk-mutator.ts # π‘ Orval custom fetch instance
β βββ network/ # π’ React Query configuration
βββ layout.tsx # Root layout with providers
βββ page.tsx # Landing page with component showcase
βββ globals.css # Global styles with CSS custom properties
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 CreateProductRequestSchema = z
.object({
name: z
.string()
.min(1)
.openapi({ description: 'Product name', example: 'Widget' }),
price: z
.number()
.min(0)
.openapi({ description: 'Price in USD', example: 29.99 }),
category: z
.string()
.openapi({ description: 'Product category', example: 'Electronics' }),
description: z
.string()
.optional()
.openapi({ description: 'Product description' })
})
.openapi('CreateProductRequest');
export const ProductSchema = z
.object({
_id: z.string().openapi({ example: '507f1f77bcf86cd799439011' }),
name: z.string(),
price: z.number(),
category: z.string(),
description: z.string().optional(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date()
})
.openapi('Product');
export type CreateProductRequest = z.infer<typeof CreateProductRequestSchema>;
export type Product = z.infer<typeof ProductSchema>;import {
registry,
createRouteConfig,
createListResponse
} from '@/lib/api/openapi';
import { CreateProductRequestSchema, ProductSchema } from './schema';
// GET /api/products
registry.registerPath(
createRouteConfig({
method: 'get',
path: '/api/products',
tags: ['Products'],
summary: 'Get all products',
responses: {
200: {
description: 'List of products',
content: {
'application/json': {
schema: createListResponse(ProductSchema)
}
}
}
}
})
);
// POST /api/products
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: z.object({
success: z.literal(true),
data: ProductSchema,
message: z.string()
})
}
}
}
}
})
);import { NextRequest, NextResponse } from 'next/server';
import { withDatabase } from '@/lib/api/middleware';
import { Product } from '@/lib/api/models';
import { CreateProductRequestSchema } from './schema';
import './openapi'; // β οΈ CRITICAL: Import to register OpenAPI 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) => {
try {
const body = await req.json();
// Validate with Zod
const validationResult = CreateProductRequestSchema.safeParse(body);
if (!validationResult.success) {
return NextResponse.json(
{ success: false, error: validationResult.error.errors[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 }
);
} catch (error) {
console.error('Create product error:', error);
return NextResponse.json(
{ success: false, error: 'Failed to create product' },
{ status: 500 }
);
}
});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 React Query hooks in
sdk/index.ts
import { Button } from '@/components/ui/Button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { useToast } from '@/context/ToastContext';
export function ExampleComponent() {
const { showToast } = useToast();
return (
<Card>
<CardHeader>
<CardTitle>Example Component</CardTitle>
</CardHeader>
<CardContent>
<Button
onClick={() => showToast('Success!', 'success')}
variant="default"
>
Click me
</Button>
</CardContent>
</Card>
);
}import { useGetApiUsers, usePostApiUsers } from 'sdk';
// Using auto-generated React Query hooks
export function UsersList() {
const { data: response, isLoading } = useGetApiUsers();
const createUser = usePostApiUsers();
if (isLoading) return <div>Loading...</div>;
return (
<div>
{response?.data?.map(user => (
<div key={user._id}>{user.name}</div>
))}
<button
onClick={() => createUser.mutate({ data: { name: 'John', email: 'john@example.com' } })}
>
Add User
</button>
</div>
);
}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 with Orval
npm run api:watch # Watch mode: OpenAPI + SDK auto-regeneration
npm run api:dev # Start dev server + watch OpenAPI + SDK
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 test # Run all checks (format, lint, type-check)Once running, access your documentation and app:
- Frontend App: http://localhost:3000 (Landing page with component showcase)
- Interactive Docs: http://localhost:3000/api/docs
- OpenAPI Spec: http://localhost:3000/api/openapi.json
- Health Check: http://localhost:3000/api/health
The system uses a two-step generation process:
- OpenAPI Generation: Imports all
openapi.tsfiles to build the OpenAPI spec - SDK Generation: Uses Orval to generate React Query hooks from the spec
- Define Zod schemas in
schema.tswith.openapi()metadata - Register routes in
openapi.tsusingregistry.registerPath() - Import
'./openapi'inroute.tsto trigger registration - Run
npm run api:generateto buildopenapi.json - Run
npm run api:sdkto generate React Query hooks insdk/
openapi.json- OpenAPI 3.0 specification (project root)sdk/index.ts- Type-safe React Query hooks and types
- Framework: Next.js 16+ (App Router) + React 19
- Styling: Tailwind CSS v4 with CSS custom properties
- Components: Custom UI library with Framer Motion
- State Management: React Query + React hooks
- Forms: React Hook Form + Zod
- Icons: Lucide React
- Language: TypeScript with ES Modules
- Database: MongoDB with Mongoose
- Validation: Zod schemas with OpenAPI extensions
- Documentation: OpenAPI 3.0 + Swagger UI
- SDK Generation: Orval for React Query hooks
- OpenAPI Tools: @asteasolutions/zod-to-openapi
- Code Quality: ESLint + Prettier + TypeScript
- Git Hooks: Husky + lint-staged
- 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- Backend Development Guide - Comprehensive backend documentation
- SDK Usage Examples - Frontend integration with React Query
- Middleware Guide - Understanding Next.js proxy vs route middleware
- 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