A complete, production-ready NestJS application with GraphQL, TypeORM, PostgreSQL, JWT authentication, and role-based access control.
-
π Authentication & Authorization
- User registration and login
- JWT-based authentication
- Password hashing with bcrypt
- Role-based access control (Admin/User)
- Protected routes with Guards
-
π GraphQL API
- Code-first approach
- Auto-generated GraphQL schema
- GraphQL Playground for testing
- Proper error handling
-
πΎ Database
- PostgreSQL with TypeORM
- Entity relationships
- Database migrations support
- Seed data for development
-
π¦ Posts Management (CRUD)
- Create, Read, Update, Delete operations
- Pagination support
- Filtering and search
- Authorization for modifications
-
π§ͺ Testing
- Unit tests for services and resolvers
- Jest testing framework
- High test coverage
- Node.js (v18 or higher)
- PostgreSQL (v12 or higher)
- npm or yarn
-
Clone the repository
git clone <repository-url> cd nestjs-graphql-auth
-
Install dependencies
npm install
-
Configure environment variables
cp .env.example .env # Edit .env with your database credentials -
Set up PostgreSQL database
CREATE DATABASE nestjs_graphql_auth;
-
Run database seeds (optional)
npm run seed
-
Start the development server
npm run start:dev
-
Access the application
- API: http://localhost:3000
- GraphQL Playground: http://localhost:3000/graphql
src/
βββ auth/ # Authentication module
β βββ dto/ # Data Transfer Objects
β βββ strategies/ # Passport JWT strategy
β βββ auth.module.ts
β βββ auth.resolver.ts
β βββ auth.service.ts
βββ users/ # Users module
β βββ dto/
β βββ entities/
β βββ users.module.ts
β βββ users.resolver.ts
β βββ users.service.ts
βββ posts/ # Posts module
β βββ dto/
β βββ entities/
β βββ posts.module.ts
β βββ posts.resolver.ts
β βββ posts.service.ts
βββ common/ # Shared resources
β βββ decorators/ # Custom decorators
β βββ guards/ # Auth & Role guards
β βββ filters/ # Exception filters
β βββ interceptors/ # Logging interceptor
βββ config/ # Configuration files
βββ database/ # Database configuration
βββ seeds/ # Seed data
βββ app.module.ts # Root module
βββ main.ts # Application entry point
mutation {
register(registerInput: {
email: "user@example.com"
firstName: "John"
lastName: "Doe"
password: "password123"
}) {
accessToken
refreshToken
user {
id
email
firstName
lastName
role
}
}
}mutation {
login(loginInput: {
email: "user@example.com"
password: "password123"
}) {
accessToken
refreshToken
user {
id
email
firstName
lastName
role
}
}
}π Next: Copy the
accessTokenfrom the response and add it to HTTP Headers to make authenticated requests.
After logging in or registering, you'll receive an accessToken. To make authenticated requests, you need to add this token to the HTTP Headers in GraphQL Playground.
mutation {
login(loginInput: {
email: "admin@example.com"
password: "admin123"
}) {
accessToken
user {
id
email
role
}
}
}Click on "HTTP HEADERS" tab at the bottom of GraphQL Playground and add:
{
"Authorization": "Bearer YOUR_ACCESS_TOKEN_HERE"
}Example:
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}π‘ Tip: The token is automatically decoded by the server to identify the current user.
Now you can call protected queries and mutations:
# Get current logged-in user info
query {
me {
id
email
firstName
lastName
role
}
}
# Create a new post (requires authentication)
mutation {
createPost(createPostInput: {
title: "My First Post"
content: "This is my post content"
published: true
}) {
id
title
content
createdAt
}
}
# Get my posts
query {
myPosts(paginationInput: { page: 1, limit: 10 }) {
items {
id
title
published
}
}
}query {
users(paginationInput: { page: 1, limit: 10 }) {
items {
id
email
firstName
lastName
role
createdAt
}
total
page
limit
totalPages
hasNextPage
hasPreviousPage
}
}query {
user(id: "user-id") {
id
email
firstName
lastName
role
posts {
id
title
published
}
}
}query {
me {
id
email
firstName
lastName
role
}
}mutation {
createUser(createUserInput: {
email: "newuser@example.com"
firstName: "New"
lastName: "User"
password: "password123"
role: USER
}) {
id
email
firstName
lastName
role
}
}mutation {
updateUser(
id: "user-id"
updateUserInput: {
firstName: "Updated"
lastName: "Name"
}
) {
id
email
firstName
lastName
}
}mutation {
removeUser(id: "user-id")
}query {
posts(
paginationInput: { page: 1, limit: 10 }
filterInput: { published: true, searchTerm: "GraphQL" }
) {
items {
id
title
content
published
createdAt
author {
id
firstName
lastName
}
}
total
page
limit
totalPages
hasNextPage
hasPreviousPage
}
}query {
post(id: "post-id") {
id
title
content
published
createdAt
updatedAt
author {
id
firstName
lastName
}
}
}query {
myPosts(paginationInput: { page: 1, limit: 10 }) {
items {
id
title
content
published
createdAt
}
total
page
limit
}
}query {
postsByUser(
userId: "user-id"
paginationInput: { page: 1, limit: 10 }
) {
items {
id
title
content
published
author {
firstName
lastName
}
}
total
}
}mutation {
createPost(createPostInput: {
title: "My New Post"
content: "This is the content of my post."
published: true
}) {
id
title
content
published
createdAt
author {
id
firstName
lastName
}
}
}mutation {
updatePost(
id: "post-id"
updatePostInput: {
title: "Updated Title"
content: "Updated content"
}
) {
id
title
content
updatedAt
}
}mutation {
deletePost(id: "post-id")
}mutation {
publishPost(id: "post-id") {
id
title
published
}
}
mutation {
unpublishPost(id: "post-id") {
id
title
published
}
}ADMIN: Full access to all resourcesUSER: Limited access, can only manage own resources
| Operation | Admin | User |
|---|---|---|
| Create Post | β | β |
| Read All Posts | β | β |
| Read Own Posts | β | β |
| Update Any Post | β | β |
| Update Own Post | β | β |
| Delete Any Post | β | β |
| Delete Own Post | β | β |
| Create User | β | β |
| Read All Users | β | β |
| Update Any User | β | β |
| Update Own Profile | β | β |
| Delete Any User | β | β |
| Delete Own Account | β | β |
# Unit tests
npm test
# Watch mode
npm run test:watch
# Coverage report
npm run test:cov
# E2E tests
npm run test:e2e- Auth Service
- Auth Resolver
- Users Service
- Users Resolver
- Posts Service
- Posts Resolver
Default users created by seed script:
| Password | Role | |
|---|---|---|
| admin@example.com | admin123 | ADMIN |
| john@example.com | password123 | USER |
| jane@example.com | password123 | USER |
| bob@example.com | password123 | USER |
Run seeds:
npm run seed| Variable | Description | Default |
|---|---|---|
DB_HOST |
PostgreSQL host | localhost |
DB_PORT |
PostgreSQL port | 5432 |
DB_USERNAME |
Database username | postgres |
DB_PASSWORD |
Database password | password |
DB_DATABASE |
Database name | nestjs_graphql_auth |
PORT |
Application port | 3000 |
JWT_SECRET |
JWT secret key | - |
JWT_EXPIRATION |
JWT expiration time | 1d |
GRAPHQL_PLAYGROUND |
Enable GraphQL Playground | true |
GRAPHQL_DEBUG |
Enable GraphQL debug mode | true |
GRAPHQL_INTROSPECTION |
Enable schema introspection | true |
# Development
npm run start:dev # Start with hot reload
# Production
npm run build # Build application
npm run start:prod # Start production build
# Testing
npm test # Run unit tests
npm run test:watch # Run tests in watch mode
npm run test:cov # Generate coverage report
npm run test:e2e # Run E2E tests
# Database
npm run seed # Run database seeds
npm run migration:generate # Generate migration
npm run migration:run # Run migrations
npm run migration:revert # Revert last migration
# Linting & Formatting
npm run lint # Run ESLint
npm run format # Format with Prettier- β Password hashing with bcrypt
- β JWT-based authentication
- β Input validation with class-validator
- β SQL injection protection via TypeORM
- β Role-based access control
- β Environment variable configuration
- β CORS enabled
This section provides a comprehensive guide to NestJS concepts used in this project, from fundamentals to advanced topics.
- NestJS Architecture Overview
- Modules
- Controllers vs Resolvers
- Services & Providers
- Dependency Injection
- Middleware
- Guards
- Interceptors
- Exception Filters
- Pipes
- Custom Decorators
- Configuration Management
- Production Best Practices
NestJS is a progressive Node.js framework built with TypeScript that uses modern JavaScript and implements design patterns like Dependency Injection, SOLID principles, and Object-Oriented Programming.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Request β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Middleware β Guards β Interceptors β Pipes β Handler β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Controller/Resolver β
β (Request Handler) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Service/Provider β
β (Business Logic) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Repository/Database β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Concepts:
- Modular Design: Application is divided into modules
- Providers: Injectable classes (services, repositories)
- Controllers/Resolvers: Handle incoming requests
- Middleware: Process requests before they reach handlers
- Guards: Control access to routes
- Interceptors: Transform request/response flow
- Pipes: Validate and transform data
Modules are the fundamental building blocks of a NestJS application. They organize related components (controllers, services, etc.) together.
// app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [AuthModule, UsersModule], // Other modules to import
controllers: [AppController], // Controllers (REST)
providers: [AppService], // Services, Repositories
exports: [AppService], // Providers to export
})
export class AppModule {}| Type | Description | Example |
|---|---|---|
| Feature Module | Encapsulates specific feature | AuthModule, UsersModule |
| Shared Module | Reusable across modules | CommonModule |
| Global Module | Available everywhere | @Global() decorator |
| Dynamic Module | Configured at runtime | ConfigModule.forRoot() |
AppModule (Root)
βββ ConfigModule (Global - env variables)
βββ TypeOrmModule (Database connection)
βββ GraphQLModule (GraphQL configuration)
βββ AuthModule
β βββ JwtModule
β βββ PassportModule
β βββ User entity repository
βββ UsersModule
β βββ User entity repository
βββ PostsModule
βββ Post entity repository
Example - Feature Module:
// auth/auth.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([User]), // Register User entity
PassportModule,
JwtModule.registerAsync({...}), // Async configuration
],
providers: [AuthService, AuthResolver, JwtStrategy],
exports: [AuthService], // Make available to other modules
})
export class AuthModule {}Providers are the core of NestJS. They can be injected as dependencies. The most common type is a Service.
A provider is any class annotated with @Injectable() that can be injected into other classes.
@Injectable()
export class AuthService {
// Business logic here
}Services contain business logic and are separated from controllers/resolvers:
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly jwtService: JwtService,
) {}
async register(registerInput: RegisterInput): Promise<AuthResponse> {
// 1. Check if user exists
const existingUser = await this.userRepository.findOne({
where: { email: registerInput.email },
});
if (existingUser) {
throw new ConflictException('Email already registered');
}
// 2. Hash password
const hashedPassword = await bcrypt.hash(registerInput.password, 10);
// 3. Create and save user
const user = this.userRepository.create({
...registerInput,
password: hashedPassword,
});
return this.userRepository.save(user);
}
}// Option 1: Short form
@Module({
providers: [AuthService],
})
// Option 2: With custom token
@Module({
providers: [
{
provide: 'AUTH_SERVICE',
useClass: AuthService,
},
],
})
// Option 3: Factory provider
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (config: ConfigService) => {
return createConnection(config.get('database'));
},
inject: [ConfigService],
},
],
})Controllers handle HTTP requests and return HTTP responses:
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(): Promise<User[]> {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): Promise<User> {
return this.usersService.findOne(id);
}
@Post()
create(@Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.create(createUserDto);
}
}Resolvers handle GraphQL operations (queries, mutations, subscriptions):
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
// Query - fetching data
@Query(() => [User], { name: 'users' })
findAll() {
return this.usersService.findAll();
}
// Query with parameters
@Query(() => User, { name: 'user' })
findOne(@Args('id') id: string) {
return this.usersService.findOne(id);
}
// Mutation - modifying data
@Mutation(() => User)
createUser(@Args('createUserInput') createUserInput: CreateUserInput) {
return this.usersService.create(createUserInput);
}
}| Aspect | REST Controllers | GraphQL Resolvers |
|---|---|---|
| Protocol | HTTP | GraphQL over HTTP |
| Operations | GET, POST, PUT, DELETE | Query, Mutation, Subscription |
| Data Shape | Fixed response structure | Client specifies exact fields |
| Endpoints | Multiple URLs | Single endpoint (/graphql) |
| Decorators | @Get(), @Post() |
@Query(), @Mutation() |
Dependency Injection (DI) is a design pattern where classes receive their dependencies from external sources rather than creating them internally.
- Loose Coupling: Classes don't depend on concrete implementations
- Testability: Easy to mock dependencies in tests
- Reusability: Same service can be used in multiple places
- Maintainability: Changes in one place don't break others
@Injectable()
export class AuthService {
// Dependencies are injected automatically
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly jwtService: JwtService,
) {}
}NestJS handles the instantiation:
1. Creates UserRepository instance
2. Creates JwtService instance
3. Creates AuthService instance with above dependencies
Constructor Injection (Recommended):
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
}Property Injection:
@Injectable()
export class UsersService {
@InjectRepository(User)
private userRepository: Repository<User>;
}Custom Provider Injection:
@Injectable()
export class MyService {
constructor(
@Inject('CUSTOM_TOKEN')
private customService: CustomService,
) {}
}ββββββββββββββββββββββββββββββββββββββββββββ
β Application Bootstrap β
ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββ
β Parse Module Dependencies β
β (Build dependency graph) β
ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββ
β Resolve Dependencies β
β (Create instances in order) β
ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββ
β Inject into Constructors β
β (Pass instances to classes) β
ββββββββββββββββββββββββββββββββββββββββββββ
Middleware is a function that executes before the route handler. It has access to the request and response objects.
- Logging/Analytics
- Authentication check
- CORS handling
- Request parsing
- Rate limiting
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl } = req;
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
this.logger.log(`${method} ${originalUrl} - ${res.statusCode} - ${duration}ms`);
});
next(); // Pass control to next middleware/handler
}
}// app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL }); // Apply to all routes
// .forRoutes(UsersController); // Apply to specific controller
// .exclude({ path: 'auth/login', method: RequestMethod.POST }) // Exclude paths
}
}For simple cases, use functions:
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
}
// Apply
consumer.apply(logger).forRoutes('*');Guards determine whether a request should be handled by the route handler. They implement authorization logic.
Request β Middleware β Guards β Interceptors β Pipes β Handler
β
βΌ
βββββββββββββββ
β Guard β ββNoβββ Throw Exception (401/403)
β (canActivate)β
βββββββββββββββ
βYes
βΌ
Continue to Handler
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Get required roles from metadata
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true; // No roles required, allow access
}
// Get user from request
const ctx = GqlExecutionContext.create(context);
const { user } = ctx.getContext().req;
// Check if user has required role
const hasRole = requiredRoles.some((role) => user.role === role);
if (!hasRole) {
throw new ForbiddenException('Insufficient permissions');
}
return true;
}
}Method-level:
@Resolver(() => Post)
export class PostsResolver {
@Mutation(() => Post)
@UseGuards(RolesGuard)
@Roles(UserRole.ADMIN) // Custom decorator
deletePost(@Args('id') id: string) {
return this.postsService.remove(id);
}
}Controller/Resolver-level:
@Resolver(() => Post)
@UseGuards(JwtAuthGuard) // All methods protected
export class PostsResolver { }Global-level:
// app.module.ts
@Module({
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard, // Applies to all routes
},
],
})| Guard | Purpose | Usage |
|---|---|---|
JwtAuthGuard |
Validate JWT token | Global + specific routes |
RolesGuard |
Check user roles | Combined with @Roles() decorator |
Interceptors intercept incoming requests and outgoing responses. They can:
- Transform responses
- Add extra logic before/after method execution
- Cache responses
- Handle timeouts
- Log requests
export interface NestInterceptor<T = any, R = any> {
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<R> | Promise<Observable<R>>;
}@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = GqlExecutionContext.create(context);
const info = ctx.getInfo();
const start = Date.now();
// Code executed BEFORE handler
this.logger.log(`Incoming: ${info.parentType.name}.${info.fieldName}`);
return next.handle().pipe(
// Code executed AFTER handler
tap((data) => {
const duration = Date.now() - start;
this.logger.log(`${info.fieldName} completed in ${duration}ms`);
}),
);
}
}Method-level:
@Query(() => User)
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
findOne(@Args('id') id: string) {
return this.usersService.findOne(id);
}Controller/Resolver-level:
@Resolver(() => User)
@UseInterceptors(LoggingInterceptor)
export class UsersResolver { }Global-level:
// app.module.ts
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(
map((data) => ({
data,
timestamp: new Date().toISOString(),
status: 'success',
})),
);
}
}Exception Filters handle exceptions thrown by your application and format the response sent to the client.
throw new BadRequestException('Invalid input');
throw new UnauthorizedException('Please login');
throw new ForbiddenException('Access denied');
throw new NotFoundException('User not found');
throw new ConflictException('Email already exists');
throw new InternalServerErrorException('Something went wrong');@Catch() // Catch all exceptions
export class GraphQLExceptionFilter implements GqlExceptionFilter {
private readonly logger = new Logger(GraphQLExceptionFilter.name);
catch(exception: any, host: ArgumentsHost) {
const gqlHost = GqlArgumentsHost.create(host);
const info = gqlHost.getInfo();
// Determine error details
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';
let code = 'INTERNAL_SERVER_ERROR';
if (exception instanceof HttpException) {
status = exception.getStatus();
message = exception.message;
code = this.getErrorCode(status);
}
// Log the error
this.logger.error(
`${info.parentType.name}.${info.fieldName}: ${message}`,
exception.stack,
);
// Return formatted error
return {
statusCode: status,
message,
code,
timestamp: new Date().toISOString(),
path: `${info.parentType.name}.${info.fieldName}`,
};
}
private getErrorCode(status: number): string {
switch (status) {
case HttpStatus.UNAUTHORIZED: return 'UNAUTHORIZED';
case HttpStatus.FORBIDDEN: return 'FORBIDDEN';
case HttpStatus.NOT_FOUND: return 'NOT_FOUND';
default: return 'INTERNAL_SERVER_ERROR';
}
}
}Method-level:
@Query(() => User)
@UseFilters(GraphQLExceptionFilter)
findOne(@Args('id') id: string) {
return this.usersService.findOne(id);
}Global-level:
// app.module.ts
@Module({
providers: [
{
provide: APP_FILTER,
useClass: GraphQLExceptionFilter,
},
],
})Pipes transform input data and validate it before it reaches the handler.
| Pipe | Purpose |
|---|---|
ValidationPipe |
Validate and transform DTOs |
ParseIntPipe |
Parse string to integer |
ParseBoolPipe |
Parse string to boolean |
ParseArrayPipe |
Parse to array |
DefaultValuePipe |
Set default value |
// main.ts
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip properties without decorators
forbidNonWhitelisted: true, // Throw error for extra properties
transform: true, // Transform to DTO instances
transformOptions: {
enableImplicitConversion: true,
},
}),
);export class CreateUserInput {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
@Matches(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain uppercase, lowercase, and number',
})
password: string;
@IsString()
@IsNotEmpty()
firstName: string;
@IsEnum(UserRole)
@IsOptional()
role?: UserRole;
}@Injectable()
export class ParseObjectIdPipe implements PipeTransform<string, string> {
transform(value: string): string {
if (!isValidObjectId(value)) {
throw new BadRequestException('Invalid ObjectId');
}
return value;
}
}
// Usage
@Get(':id')
findOne(@Param('id', ParseObjectIdPipe) id: string) {
return this.service.findOne(id);
}Decorators add metadata to classes, methods, or parameters.
Metadata Decorator:
// decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);
// Usage
@Roles(UserRole.ADMIN)
@Mutation(() => User)
deleteUser(@Args('id') id: string) { }Parameter Decorator:
// decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const gqlCtx = GqlExecutionContext.create(ctx);
return gqlCtx.getContext().req.user;
},
);
// Usage
@Query(() => User)
me(@CurrentUser() user: User) {
return user;
}Method Decorator:
// decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// Usage in Guard
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;NestJS provides the ConfigModule for environment-based configuration.
// app.module.ts
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // Available everywhere
envFilePath: '.env', // Load from .env file
load: [appConfig, dbConfig], // Load configuration objects
}),
],
})// config/app.config.ts
export default registerAs('app', () => ({
port: parseInt(process.env.PORT, 10) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
graphql: {
playground: process.env.GRAPHQL_PLAYGROUND === 'true',
debug: process.env.GRAPHQL_DEBUG === 'true',
},
}));
// config/database.config.ts
export default registerAs('database', () => ({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
}));// Using ConfigService
@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}
getPort(): number {
return this.configService.get<number>('app.port');
// Or: this.configService.get('PORT');
}
}
// Async module configuration
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('database.host'),
port: configService.get('database.port'),
// ...
}),
inject: [ConfigService],
}),// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 1. Enable CORS with specific origins
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || false,
credentials: true,
});
// 2. Helmet for security headers
app.use(helmet());
// 3. Rate limiting
app.use(
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
}),
);
// 4. Global validation
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
// 5. Graceful shutdown
app.enableShutdownHooks();
await app.listen(process.env.PORT || 3000);
}// Enable compression
app.use(compression());
// Use fastify adapter for better performance
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);// Custom logger
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log'],
});
// Winston for production
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
});
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));// Install: npm install @nestjs/terminus
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database'),
]);
}
}// app.module.ts
const isProduction = process.env.NODE_ENV === 'production';
@Module({
imports: [
GraphQLModule.forRoot({
debug: !isProduction,
playground: !isProduction,
introspection: !isProduction,
}),
],
})- 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.
If you encounter any issues or have questions:
- Check the documentation above
- Review the code comments
- Open an issue in the repository