A reusable NestJS starter template with essential backend configurations, best practices, and development tools.
A reusable NestJS 11 base project with conventions and common infrastructure for building multiple applications. This repository focuses on the application's core foundation, including environment configuration, validation, exception handling, logging, Swagger documentation, and API security.
| Area | Technology / Approach |
|---|---|
| Environment & Configuration | @nestjs/config, Joi, and typed configuration namespaces |
| Validation & DTOs | class-validator, class-transformer, and a global ValidationPipe |
| Exception Handling | Global exception filter, stable error codes, and a standardized error envelope |
| Logging | nestjs-pino + pino-http, request IDs, and sensitive data redaction |
| Swagger & API Conventions | /docs, standardized response envelopes, and reusable decorators for success, error, and pagination responses |
| Security & API Protection | Helmet, CORS, rate limiting, JWT guard, role guard, and @Public() |
# 1. Install dependencies
npm install
# 2. Create the environment file
cp .env.example .env
# 3. Generate a secure JWT secret (at least 32 characters)
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"
Update JWT_SECRET in your .env file with the generated value.
# Start in development mode
npm run start:dev
| Resource | URL |
|---|---|
| API Base URL | http://localhost:3000/api/v1 |
| Health Check | http://localhost:3000/health |
| Swagger UI | http://localhost:3000/docs |
| OpenAPI JSON | http://localhost:3000/docs-json |
npm run build
npm run start:prod
npm run lint
npm run format
npm test
npm run test:e2e
src/
├── app.module.ts # Wires all global providers
├── app.controller.ts
├── app.service.ts
├── main.ts # Bootstrap: Helmet, CORS, prefix, versioning, Swagger
├── config/
│ ├── app.config.ts # "app" configuration namespace
│ ├── auth.config.ts # "auth" configuration namespace
│ ├── logging.config.ts # "logging" configuration namespace
│ ├── swagger.config.ts # "swagger" configuration namespace
│ ├── throttler.config.ts # "throttler" configuration namespace
│ ├── env.validation.ts # Joi schema and fail-fast environment validation
│ └── setup-swagger.ts
├── common/
│ ├── decorators/ # @Public, @Roles, @CurrentUser, Swagger decorators
│ ├── dto/ # Pagination and response/error envelope DTOs
│ ├── enums/
│ ├── errors/ # AppException + ErrorCode
│ ├── filters/ # AllExceptionsFilter
│ ├── guards/ # JwtAuthGuard, RolesGuard
│ ├── interceptors/ # ResponseTransformInterceptor
│ ├── interfaces/
│ └── validation/ # ValidationPipe factory and error flattening
└── modules/
├── auth/ # JWT authentication + demo login
└── health/ # GET /health
See .env.example for the complete list of environment variables.
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
development |
Supported values: development, test, production |
PORT |
3000 |
Application port |
API_PREFIX |
api |
API prefix. Final route format: /{prefix}/v{version} |
API_VERSION |
1 |
URI versioning |
CORS_ORIGINS |
* |
Comma-separated list of allowed origins |
JWT_SECRET |
Required | Minimum 32 characters. The application will not start if missing |
JWT_EXPIRES_IN |
1h |
JWT expiration time |
THROTTLE_TTL_MS |
60000 |
Rate-limiting window in milliseconds |
THROTTLE_LIMIT |
100 |
Maximum requests per IP per window |
LOG_LEVEL |
info |
Log level, from fatal to trace |
LOG_PRETTY |
true in development |
Set to false in production to output JSON logs |
SWAGGER_ENABLED |
true |
Enables or disables Swagger documentation |
DEMO_AUTH_ENABLED |
true in development |
Automatically disabled in production |
Environment variables are validated during application bootstrap. If a required variable is missing or invalid, the application reports the affected variable and exits instead of continuing with an invalid configuration.
/api/v1/... # Versioned API routes
/health # Health check, without prefix or version
/docs # Swagger UI
All successful responses are wrapped by ResponseTransformInterceptor.
Example:
{
"success": true,
"data": {
"id": "..."
},
"requestId": "6a4f...",
"timestamp": "2026-01-01T00:00:00.000Z"
}
For endpoints that need to return raw responses, such as files, streams, or webhooks, use @SkipResponseTransform().
HTTP 204 No Content responses are also preserved without transformation.
All exceptions are handled by AllExceptionsFilter and normalized into a consistent error structure.
Example:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed.",
"details": [
{
"field": "email",
"messages": ["email must be an email"]
}
]
},
"requestId": "6a4f...",
"path": "/api/v1/auth/login",
"method": "POST",
"timestamp": "2026-01-01T00:00:00.000Z"
}
Clients should rely on error.code rather than the human-readable message.
The list of supported error codes is defined in src/common/errors/error-code.ts.
Server-side errors (HTTP 5xx) return a generic message to prevent internal details and stack traces from being exposed.
pino-http generates an x-request-id for each request or reuses the ID provided by the client.
The request ID is included in:
This makes it easier to trace requests across application logs.
The global ValidationPipe enables the following options:
whitelist: Removes properties that do not have validation decorators.forbidNonWhitelisted: Rejects requests containing unexpected properties.transform: Automatically transforms incoming payloads into DTO instances.Requests containing unexpected fields are rejected with a VALIDATION_ERROR.
export class CreateUserDto {
@ApiProperty({ example: '[email protected]' })
@IsEmail()
email!: string;
@ApiProperty({ minLength: 8 })
@IsString()
@MinLength(8)
password!: string;
}
Reusable pagination DTOs and response decorators are provided.
@Get()
@ApiPaginatedEnvelopeResponse(UserDto)
findAll(@Query() query: PaginationQueryDto) {
return new PaginatedResponseDto(items, total, query);
}
Use these conventions to keep pagination behavior and API documentation consistent across modules.
Use AppException to represent business-level errors.
Example:
throw new AppException(
ErrorCode.CONFLICT,
'Email already exists.',
HttpStatus.CONFLICT,
);
AppException accepts:
code: A stable application error codemessage: A human-readable error messagedetailsAllExceptionsFilter converts the exception into the standardized error envelope.
The application uses nestjs-pino and pino-http for structured logging.
pino-pretty.authorization, cookie, and set-cookie are redacted./health requests are excluded from logging to reduce noise.PinoLogger from nestjs-pino, or use NestJS's Logger, which is routed through Pino in main.ts.Access the interactive API documentation at /docs.
Swagger is configured with the following features:
access-token security scheme.persistAuthorization to retain authorization credentials when reloading the page.| Decorator | Purpose |
|---|---|
@ApiOkEnvelopeResponse(Model) |
Documents a successful HTTP 200 response |
@ApiCreatedEnvelopeResponse(Model) |
Documents a successful HTTP 201 response |
@ApiPaginatedEnvelopeResponse(Model) |
Documents a paginated response |
@ApiCommonErrorResponses() |
Documents common API error responses |
Example:
@Get(':id')
@ApiOkEnvelopeResponse(UserDto)
@ApiCommonErrorResponses()
findOne(@Param('id') id: string) {}
These decorators help keep the Swagger documentation aligned with the actual API response format.
The base project includes several security mechanisms.
CORS_ORIGINS, with credentials support.@nestjs/throttler. Login requests have a stricter limit of five requests per minute.@Public().@Roles(Role.ADMIN) to restrict access based on user roles.@CurrentUser() user: AuthenticatedUser to retrieve the authenticated user, or @CurrentUser('sub') userId: string to retrieve the subject ID.TRUST_PROXY=true when running behind a reverse proxy so rate limiting can use the correct client IP address.The base project includes a demo login flow in AuthService, configured through DEMO_AUTH_EMAIL and DEMO_AUTH_PASSWORD.
This allows you to test authentication without integrating a user database immediately.
Example login request:
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"Admin@123456"}'
After obtaining an access token, call a protected endpoint:
curl http://localhost:3000/api/v1/auth/me \
-H "Authorization: Bearer <accessToken>"
The credentials above are illustrative defaults; use the values configured in your .env file.
Before using this base project in a real application, replace the demo credential verification logic in src/modules/auth/auth.service.ts with a proper user store and secure password hashing, such as Argon2 or bcrypt.
Demo authentication is automatically disabled in production unless DEMO_AUTH_ENABLED=true is explicitly configured.
Organize new features under src/modules/ using a consistent module structure.
Example: adding a users module.
src/modules/users/
├── dto/
│ └── create-user.dto.ts
├── users.controller.ts
├── users.module.ts
└── users.service.ts
Controllers are protected by the global JWT guard by default.
When implementing new endpoints:
@Public() only for endpoints that should be publicly accessible.@Roles(...) for endpoints that require role-based authorization.These conventions help maintain consistency as the application grows and make the base project easier to reuse across different services.