Node.js Backend Development Expert
Purpose
Provide expert guidance on Node.js backend development including Express, NestJS, TypeScript, API design, database integration, and production best practices.
When to Use This Skill
- Building REST APIs with Express or NestJS
- TypeScript backend development
- Database integration (PostgreSQL, MongoDB)
- Authentication and authorization
- Error handling and logging
- Testing Node.js applications
- Performance optimization
- Production deployment
Express.js API Structure
src/
├── app.ts
├── server.ts
├── config/
│ ├── database.ts
│ └── environment.ts
├── middleware/
│ ├── auth.ts
│ ├── errorHandler.ts
│ └── validation.ts
├── routes/
│ ├── index.ts
│ ├── users.ts
│ └── posts.ts
├── controllers/
│ ├── users.controller.ts
│ └── posts.controller.ts
├── services/
│ ├── users.service.ts
│ └── posts.service.ts
├── models/
│ ├── user.model.ts
│ └── post.model.ts
├── utils/
│ ├── logger.ts
│ └── validation.ts
└── types/
└── express.d.ts
Express + TypeScript Example
app.ts
import express, { Express } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';
import { errorHandler } from './middleware/errorHandler';
import { requestLogger } from './middleware/logger';
import routes from './routes';
export function createApp(): Express {
const app = express();
// Security
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true
}));
// Parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(compression());
// Logging
app.use(requestLogger);
// Routes
app.use('/api/v1', routes);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Error handling
app.use(errorHandler);
return app;
}
Controller
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/users.service';
import { CreateUserDto, UpdateUserDto } from '../dto/user.dto';
import { validate } from '../utils/validation';
import { AppError } from '../utils/errors';
export class UserController {
constructor(private userService: UserService) {}
async create(req: Request, res: Response, next: NextFunction) {
try {
const dto = await validate(CreateUserDto, req.body);
const user = await this.userService.create(dto);
res.status(201).json({
success: true,
data: user
});
} catch (error) {
next(error);
}
}
async getById(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
const user = await this.userService.findById(id);
if (!user) {
throw new AppError('User not found', 404);
}
res.json({
success: true,
data: user
});
} catch (error) {
next(error);
}
}
async update(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
const dto = await validate(UpdateUserDto, req.body);
const user = await this.userService.update(id, dto);
res.json({
success: true,
data: user
});
} catch (error) {
next(error);
}
}
async delete(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
await this.userService.delete(id);
res.status(204).send();
} catch (error) {
next(error);
}
}
}
Service Layer
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcrypt';
import { CreateUserDto, UpdateUserDto } from '../dto/user.dto';
import { AppError } from '../utils/errors';
export class UserService {
constructor(private prisma: PrismaClient) {}
async create(dto: CreateUserDto) {
const existing = await this.prisma.user.findUnique({
where: { email: dto.email }
});
if (existing) {
throw new AppError('Email already exists', 409);
}
const hashedPassword = await bcrypt.hash(dto.password, 10);
return this.prisma.user.create({
data: {
...dto,
password: hashedPassword
},
select: {
id: true,
email: true,
name: true,
createdAt: true
}
});
}
async findById(id: string) {
return this.prisma.user.findUnique({
where: { id },
select: {
id: true,
email: true,
name: true,
createdAt: true
}
});
}
async update(id: string, dto: UpdateUserDto) {
return this.prisma.user.update({
where: { id },
data: dto,
select: {
id: true,
email: true,
name: true,
updatedAt: true
}
});
}
async delete(id: string) {
await this.prisma.user.delete({
where: { id }
});
}
}
Middleware
// Authentication
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors';
interface JWTPayload {
userId: string;
email: string;
}
export async function authenticate(
req: Request,
res: Response,
next: NextFunction
) {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new AppError('No token provided', 401);
}
const payload = jwt.verify(
token,
process.env.JWT_SECRET!
) as JWTPayload;
req.user = payload;
next();
} catch (error) {
next(new AppError('Invalid token', 401));
}
}
// Error Handler
export function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: {
message: err.message,
code: err.code
}
});
}
console.error('Unexpected error:', err);
res.status(500).json({
success: false,
error: {
message: 'Internal server error'
}
});
}
NestJS Example
Module
import { Module } from '@nestjs/common';
import { UserController } from './users.controller';
import { UserService } from './users.service';
import { PrismaService } from '../prisma/prisma.service';
@Module({
controllers: [UserController],
providers: [UserService, PrismaService],
exports: [UserService]
})
export class UsersModule {}
Controller
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
UseGuards,
HttpCode,
HttpStatus
} from '@nestjs/common';
import { UserService } from './users.service';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get(':id')
@UseGuards(JwtAuthGuard)
async findOne(@Param('id') id: string) {
return this.userService.findById(id);
}
@Put(':id')
@UseGuards(JwtAuthGuard)
async update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto
) {
return this.userService.update(id, updateUserDto);
}
@Delete(':id')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.NO_CONTENT)
async remove(@Param('id') id: string) {
return this.userService.delete(id);
}
}
Service
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async create(createUserDto: CreateUserDto) {
return this.prisma.user.create({
data: createUserDto
});
}
async findById(id: string) {
const user = await this.prisma.user.findUnique({
where: { id }
});
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async update(id: string, updateUserDto: UpdateUserDto) {
await this.findById(id);
return this.prisma.user.update({
where: { id },
data: updateUserDto
});
}
async delete(id: string) {
await this.findById(id);
await this.prisma.user.delete({
where: { id }
});
}
}
Testing
import request from 'supertest';
import { createApp } from '../app';
describe('User API', () => {
let app: Express;
beforeAll(() => {
app = createApp();
});
describe('POST /api/v1/users', () => {
it('should create a new user', async () => {
const response = await request(app)
.post('/api/v1/users')
.send({
email: 'test@example.com',
password: 'password123',
name: 'Test User'
});
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.data.email).toBe('test@example.com');
});
});
});
This skill ensures production-ready, scalable Node.js applications.