
Backend Coding
- 24 installs
- 14 repo stars
- Updated January 23, 2026
- dauquangthanh/hanoi-rainbow
Backend Coding is an agent skill that teaches API and service implementation patterns so developers can ship scalable, secure server-side code.
About
The backend-coding skill walks through designing endpoints, database layers, authentication, caching, queues, and microservices with production practices. It targets Node.js, Python, Java, and Go stacks and references deeper API and data guides. Reach for it when you are implementing server-side features, APIs, or integrations and want opinionated patterns instead of one-off snippets.
- REST resource modeling and validation patterns
- Repository and ORM data access examples
- Auth, caching, and message queue guidance
- Production security and testing practices
Backend Coding by the numbers
- 24 all-time installs (skills.sh)
- Ranked #3,424 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dauquangthanh/hanoi-rainbow --skill backend-codingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 14 |
| Last updated | January 23, 2026 |
| Repository | dauquangthanh/hanoi-rainbow ↗ |
How do you structure backend APIs, data access, and cross-cutting concerns without reinventing error-prone boilerplate?
Implement production APIs, data layers, auth, caching, queues, and tests across Node, Python, Java, and Go.
Who is it for?
Developers actively building REST or GraphQL services with databases, auth, and async processing needs.
Skip if: Pure infrastructure provisioning with no application code to write.
When should I use this skill?
Users mention backend development, server-side code, APIs, databases, or microservices implementation.
What you get
Production-oriented backend code patterns for APIs, persistence, auth, caching, queues, and tests.
Files
Backend Coding
Build production-ready backend services with proper architecture, security, performance optimization, and testing.
Core Development Workflow
Follow this systematic approach for backend implementation:
1. Design API Endpoints
Define clear, RESTful API contracts before implementation.
REST API Design Pattern:
Resource-based URLs (use plural nouns):
✅ GET /api/v1/users - List users (paginated)
✅ GET /api/v1/users/:id - Get user by ID
✅ POST /api/v1/users - Create new user
✅ PUT /api/v1/users/:id - Replace entire user
✅ PATCH /api/v1/users/:id - Update user fields
✅ DELETE /api/v1/users/:id - Delete user
❌ Avoid verb-based URLs:
❌ /api/v1/getUsers
❌ /api/v1/createUserBasic Example (Express.js):
router.get('/users',
query('page').optional().isInt({ min: 1 }).toInt(),
query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const page = req.query.page as number || 1;
const limit = req.query.limit as number || 20;
const offset = (page - 1) * limit;
const { users, total } = await userService.findAll({
limit, offset
});
res.json({
data: users,
pagination: { page, limit, total }
});
} catch (error) {
next(error);
}
}
);For complete patterns: api-design.md
2. Implement Database Layer
Use repository pattern for clean separation and testability.
Repository Pattern (TypeORM):
export class UserRepository {
private repository: Repository<User>;
async findAll(params: { search?: string; limit: number; offset: number }) {
const queryBuilder = this.repository
.createQueryBuilder('user')
.orderBy('user.createdAt', 'DESC');
if (params.search) {
queryBuilder.where(
'user.name ILIKE :search OR user.email ILIKE :search',
{ search: `%${params.search}%` }
);
}
return queryBuilder
.take(params.limit)
.skip(params.offset)
.getManyAndCount();
}
async create(userData: UserCreate): Promise<User> {
const hashedPassword = await bcrypt.hash(userData.password, 12);
const user = this.repository.create({
...userData,
password: hashedPassword
});
return this.repository.save(user);
}
}For detailed patterns: database-patterns.md
3. Implement Authentication
Secure JWT-based authentication with refresh tokens.
JWT Authentication Pattern:
export class AuthService {
private readonly JWT_SECRET = process.env.JWT_SECRET!;
private readonly ACCESS_TOKEN_EXPIRY = '15m';
private readonly REFRESH_TOKEN_EXPIRY = '7d';
async login(email: string, password: string) {
const user = await userRepository.findByEmail(email);
if (!user || !await bcrypt.compare(password, user.password)) {
throw new UnauthorizedError('Invalid credentials');
}
const accessToken = this.generateAccessToken(user);
const refreshToken = this.generateRefreshToken(user);
await tokenRepository.create({
userId: user.id,
token: refreshToken,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
return { accessToken, refreshToken, user };
}
generateAccessToken(user: User): string {
return jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
this.JWT_SECRET,
{ expiresIn: this.ACCESS_TOKEN_EXPIRY }
);
}
}
// Middleware
export const authenticate = async (req, res, next) => {
const token = req.headers.authorization?.substring(7);
if (!token) {
return res.status(401).json({ error: 'Missing token' });
}
try {
req.user = authService.verifyAccessToken(token);
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};For complete implementation: authentication-and-authorization.md
4. Implement Caching
Use Redis for performance optimization with cache-aside pattern.
Caching Pattern:
export class CacheService {
private redis: Redis;
private readonly DEFAULT_TTL = 3600; // 1 hour
async getOrSet<T>(
key: string,
fetchFn: () => Promise<T>,
ttl: number = this.DEFAULT_TTL
): Promise<T> {
// Try cache first
const cached = await this.redis.get(key);
if (cached) return JSON.parse(cached);
// Fetch from database
const data = await fetchFn();
await this.redis.setex(key, ttl, JSON.stringify(data));
return data;
}
async invalidate(pattern: string): Promise<void> {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) await this.redis.del(...keys);
}
}
// Usage in service
export class UserService {
async findById(id: string): Promise<User | null> {
return cache.getOrSet(
`user:${id}`,
() => repository.findById(id),
3600
);
}
async update(id: string, updates: Partial<User>): Promise<User | null> {
const user = await repository.update(id, updates);
await cache.invalidate(`user:${id}`);
await cache.invalidate(`users:list:*`);
return user;
}
}For advanced strategies: caching-strategies.md
5. Implement Error Handling
Global error handling with custom error classes.
Error Handling Pattern:
// Custom error classes
export class AppError extends Error {
constructor(
public statusCode: number,
message: string,
public isOperational: boolean = true
) {
super(message);
Error.captureStackTrace(this, this.constructor);
}
}
export class ValidationError extends AppError {
constructor(message: string, public errors: any[]) {
super(400, message);
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = 'Unauthorized') {
super(401, message);
}
}
export class NotFoundError extends AppError {
constructor(message: string = 'Resource not found') {
super(404, message);
}
}
// Global error handler middleware
export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: { message: err.message }
});
}
console.error('Unexpected error:', err);
res.status(500).json({ error: { message: 'Internal server error' } });
};
// Async handler wrapper
export const asyncHandler = (fn: Function) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};6. Write Tests
Write comprehensive unit and integration tests.
Testing Pattern:
describe('User API', () => {
beforeAll(async () => {
await AppDataSource.initialize();
});
afterAll(async () => {
await AppDataSource.destroy();
});
beforeEach(async () => {
await AppDataSource.synchronize(true);
});
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: 'SecurePass123!',
name: 'Test User'
})
.expect(201);
expect(response.body.data).toMatchObject({
email: 'test@example.com',
name: 'Test User'
});
expect(response.body.data.password).toBeUndefined();
});
it('should return 400 for invalid email', async () => {
await request(app)
.post('/api/v1/users')
.send({
email: 'invalid-email',
password: 'SecurePass123!',
name: 'Test User'
})
.expect(400);
});
});
});Framework-Specific Guides
Load detailed implementation guides for specific frameworks:
- [nodejs-development.md](references/nodejs-development.md) - Express, NestJS, Fastify, middleware, async handling
- [python-development.md](references/python-development.md) - Django, Flask, FastAPI, async/await, decorators
Technology-Specific Patterns
Load detailed patterns for specific technologies:
- [api-design.md](references/api-design.md) - REST, GraphQL, gRPC, versioning, documentation
- [database-patterns.md](references/database-patterns.md) - ORMs, query optimization, transactions, migrations
- [authentication-and-authorization.md](references/authentication-and-authorization.md) - JWT, OAuth, RBAC, session management
- [caching-strategies.md](references/caching-strategies.md) - Redis patterns, cache invalidation, distributed caching
- [microservices.md](references/microservices.md) - Service communication, API gateways, circuit breakers
Production-Ready Checklist
Before deployment, verify:
Security:
☐ Input validation on all endpoints (express-validator, Pydantic)
☐ SQL injection prevention (parameterized queries only)
☐ Password hashing with bcrypt/argon2 (cost factor ≥12)
☐ JWT tokens expire within 15 minutes, refresh tokens within 7 days
☐ Rate limiting: 100 req/min per user, 1000 req/min per IP
☐ CORS configured (not '*' in production)
☐ Environment variables for all secrets
☐ HTTPS only (TLS 1.3 minimum)Performance:
☐ Database indexes on query columns
☐ Connection pooling configured (10-20 connections)
☐ Caching frequently accessed data (Redis, 1-hour TTL)
☐ Pagination for large result sets (limit ≤100 items)
☐ Async operations for I/O (non-blocking)Code Quality:
☐ Repository pattern for data access
☐ Dependency injection for testability
☐ Global error handling with custom error classes
☐ Structured logging with request IDs
☐ Test coverage ≥80% (unit + integration)
☐ API documentation (OpenAPI/Swagger)
☐ Health check endpoint (/health)
☐ Graceful shutdown handlingCritical Security Principles
Never trust user input - Validate everything Use parameterized queries - Prevent SQL injection Hash passwords - bcrypt with cost factor 12+, never store plain text Expire tokens quickly - 15min access tokens, 7day refresh tokens Use HTTPS only - TLS 1.3 minimum
Critical Performance Principles
Cache frequently accessed data - Redis with appropriate TTL (typically 1 hour) Use database indexes - On all query columns Paginate large result sets - Max 100 items per page Use connection pooling - 10-20 connections Async operations for I/O - Don't block the event loop
API Design and Implementation
This reference covers REST API design, GraphQL implementation, gRPC services, API versioning, documentation, and best practices.
RESTful API Design
Resource Naming Conventions
Good Examples:
GET /api/users # List users
GET /api/users/{id} # Get specific user
POST /api/users # Create user
PATCH /api/users/{id} # Update user
DELETE /api/users/{id} # Delete user
GET /api/users/{id}/posts # Get user's posts
POST /api/users/{id}/posts # Create post for user
GET /api/posts?author={id} # Query posts by author
GET /api/posts?sort=created_at&order=desc
Bad Examples:
GET /api/getUsers # Avoid verbs in URLs
POST /api/user/create # Avoid verbs
GET /api/users/get/{id} # Redundant
DELETE /api/deleteUser/{id} # Inconsistent namingHTTP Status Codes
// Success codes
200 OK // Successful GET, PATCH, PUT, DELETE
201 Created // Successful POST
204 No Content // Successful DELETE, no response body
// Client error codes
400 Bad Request // Invalid request data
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Authenticated but not authorized
404 Not Found // Resource doesn't exist
409 Conflict // Resource conflict (e.g., duplicate email)
422 Unprocessable Entity // Validation error
429 Too Many Requests // Rate limit exceeded
// Server error codes
500 Internal Server Error // Unexpected server error
502 Bad Gateway // Upstream service error
503 Service Unavailable // Temporary unavailability
504 Gateway Timeout // Upstream timeoutResponse Format Standards
// Success response
interface SuccessResponse<T> {
data: T;
meta?: {
page?: number;
limit?: number;
total?: number;
totalPages?: number;
};
}
// Error response
interface ErrorResponse {
error: {
code: string;
message: string;
details?: any[];
};
}
// Validation error response
interface ValidationErrorResponse {
error: {
code: 'VALIDATION_ERROR';
message: string;
details: Array<{
field: string;
message: string;
code: string;
}>;
};
}
// Example implementations
app.get('/api/users', async (req, res) => {
const { page = 1, limit = 10 } = req.query;
const users = await userService.findAll(page, limit);
const total = await userService.count();
res.json({
data: users,
meta: {
page: Number(page),
limit: Number(limit),
total,
totalPages: Math.ceil(total / limit)
}
});
});
app.post('/api/users', async (req, res) => {
try {
const user = await userService.create(req.body);
res.status(201).json({ data: user });
} catch (error) {
if (error instanceof ValidationError) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input data',
details: error.details
}
});
}
if (error instanceof ConflictError) {
return res.status(409).json({
error: {
code: 'RESOURCE_CONFLICT',
message: error.message
}
});
}
throw error;
}
});Pagination Strategies
// Offset-based pagination
interface OffsetPaginationParams {
page: number;
limit: number;
}
async function getUsers(params: OffsetPaginationParams) {
const offset = (params.page - 1) * params.limit;
const [users, total] = await Promise.all([
db.user.findMany({
skip: offset,
take: params.limit,
orderBy: { createdAt: 'desc' }
}),
db.user.count()
]);
return {
data: users,
meta: {
page: params.page,
limit: params.limit,
total,
totalPages: Math.ceil(total / params.limit),
hasNext: params.page < Math.ceil(total / params.limit),
hasPrev: params.page > 1
}
};
}
// Cursor-based pagination (better for large datasets)
interface CursorPaginationParams {
cursor?: string;
limit: number;
}
async function getUsersCursor(params: CursorPaginationParams) {
const users = await db.user.findMany({
take: params.limit + 1, // Fetch one extra to check if there are more
...(params.cursor && {
cursor: { id: params.cursor },
skip: 1 // Skip the cursor itself
}),
orderBy: { createdAt: 'desc' }
});
const hasMore = users.length > params.limit;
const data = hasMore ? users.slice(0, -1) : users;
return {
data,
meta: {
nextCursor: hasMore ? data[data.length - 1].id : null,
hasMore
}
};
}
// Keyset pagination (most efficient)
interface KeysetPaginationParams {
afterId?: string;
afterCreatedAt?: Date;
limit: number;
}
async function getUsersKeyset(params: KeysetPaginationParams) {
const users = await db.user.findMany({
take: params.limit + 1,
where: params.afterId ? {
OR: [
{
createdAt: { gt: params.afterCreatedAt }
},
{
createdAt: params.afterCreatedAt,
id: { gt: params.afterId }
}
]
} : undefined,
orderBy: [
{ createdAt: 'desc' },
{ id: 'desc' }
]
});
const hasMore = users.length > params.limit;
const data = hasMore ? users.slice(0, -1) : users;
const lastItem = data[data.length - 1];
return {
data,
meta: {
nextCursor: hasMore ? {
afterId: lastItem.id,
afterCreatedAt: lastItem.createdAt
} : null,
hasMore
}
};
}Filtering, Sorting, and Search
interface QueryParams {
// Filtering
status?: string;
role?: string;
createdAfter?: string;
createdBefore?: string;
// Sorting
sortBy?: string;
order?: 'asc' | 'desc';
// Search
search?: string;
// Pagination
page?: number;
limit?: number;
}
async function getUsers(params: QueryParams) {
const {
status,
role,
createdAfter,
createdBefore,
sortBy = 'createdAt',
order = 'desc',
search,
page = 1,
limit = 10
} = params;
// Build where clause
const where: any = {};
if (status) {
where.status = status;
}
if (role) {
where.role = role;
}
if (createdAfter || createdBefore) {
where.createdAt = {};
if (createdAfter) where.createdAt.gte = new Date(createdAfter);
if (createdBefore) where.createdAt.lte = new Date(createdBefore);
}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } }
];
}
// Build order clause
const orderBy: any = {};
orderBy[sortBy] = order;
const offset = (page - 1) * limit;
const [users, total] = await Promise.all([
db.user.findMany({
where,
orderBy,
skip: offset,
take: limit
}),
db.user.count({ where })
]);
return {
data: users,
meta: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
}
// Usage
// GET /api/users?status=active&role=admin&sortBy=name&order=asc&page=1&limit=20
// GET /api/users?search=john&createdAfter=2024-01-01API Versioning
// URL versioning (most common)
app.use('/api/v1/users', usersV1Router);
app.use('/api/v2/users', usersV2Router);
// Header versioning
app.use('/api/users', (req, res, next) => {
const version = req.headers['api-version'] || '1';
if (version === '2') {
return usersV2Router(req, res, next);
}
return usersV1Router(req, res, next);
});
// Accept header versioning
app.use('/api/users', (req, res, next) => {
const accept = req.headers.accept || '';
if (accept.includes('application/vnd.api.v2+json')) {
return usersV2Router(req, res, next);
}
return usersV1Router(req, res, next);
});
// Query parameter versioning
app.use('/api/users', (req, res, next) => {
const version = req.query.version || '1';
if (version === '2') {
return usersV2Router(req, res, next);
}
return usersV1Router(req, res, next);
});GraphQL Implementation
Schema Definition
# schema.graphql
type User {
id: ID!
email: String!
name: String!
role: Role!
posts: [Post!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
comments: [Comment!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: DateTime!
}
enum Role {
USER
ADMIN
}
type Query {
user(id: ID!): User
users(
page: Int = 1
limit: Int = 10
filter: UserFilter
): UserConnection!
post(id: ID!): Post
posts(
page: Int = 1
limit: Int = 10
filter: PostFilter
): PostConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
}
type Subscription {
postCreated: Post!
postUpdated(id: ID!): Post!
}
input UserFilter {
role: Role
search: String
}
input PostFilter {
published: Boolean
authorId: ID
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
input UpdateUserInput {
email: String
name: String
}
input CreatePostInput {
title: String!
content: String!
published: Boolean
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
scalar DateTimeResolvers Implementation
// resolvers/user.resolvers.ts
import { GraphQLError } from 'graphql';
import { UserService } from '../services/user.service';
import { PostService } from '../services/post.service';
interface Context {
user?: {
id: string;
email: string;
role: string;
};
services: {
userService: UserService;
postService: PostService;
};
}
export const userResolvers = {
Query: {
user: async (_: any, { id }: { id: string }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' }
});
}
return context.services.userService.getUserById(id);
},
users: async (
_: any,
{ page, limit, filter }: { page: number; limit: number; filter?: any },
context: Context
) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' }
});
}
const result = await context.services.userService.getUsers({
page,
limit,
filter
});
return {
edges: result.data.map((user, index) => ({
node: user,
cursor: Buffer.from(`${user.id}`).toString('base64')
})),
pageInfo: {
hasNextPage: page < result.meta.totalPages,
hasPreviousPage: page > 1,
startCursor: result.data.length > 0
? Buffer.from(`${result.data[0].id}`).toString('base64')
: null,
endCursor: result.data.length > 0
? Buffer.from(`${result.data[result.data.length - 1].id}`).toString('base64')
: null
},
totalCount: result.meta.total
};
}
},
Mutation: {
createUser: async (
_: any,
{ input }: { input: CreateUserInput },
context: Context
) => {
return context.services.userService.createUser(input);
},
updateUser: async (
_: any,
{ id, input }: { id: string; input: UpdateUserInput },
context: Context
) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' }
});
}
// Check authorization
if (context.user.id !== id && context.user.role !== 'ADMIN') {
throw new GraphQLError('Not authorized', {
extensions: { code: 'FORBIDDEN' }
});
}
return context.services.userService.updateUser(id, input);
},
deleteUser: async (_: any, { id }: { id: string }, context: Context) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new GraphQLError('Admin access required', {
extensions: { code: 'FORBIDDEN' }
});
}
await context.services.userService.deleteUser(id);
return true;
}
},
User: {
// Field resolver for posts
posts: async (parent: any, _: any, context: Context) => {
return context.services.postService.getPostsByAuthor(parent.id);
}
}
};
// server.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { readFileSync } from 'fs';
import { userResolvers } from './resolvers/user.resolvers';
import { postResolvers } from './resolvers/post.resolvers';
import { verifyToken } from './utils/auth';
const typeDefs = readFileSync('./schema.graphql', 'utf-8');
const server = new ApolloServer({
typeDefs,
resolvers: [userResolvers, postResolvers],
formatError: (error) => {
// Custom error formatting
return {
message: error.message,
code: error.extensions?.code || 'INTERNAL_SERVER_ERROR',
locations: error.locations,
path: error.path
};
}
});
await server.start();
app.use(
'/graphql',
express.json(),
expressMiddleware(server, {
context: async ({ req }) => {
let user;
const token = req.headers.authorization?.replace('Bearer ', '');
if (token) {
try {
user = verifyToken(token);
} catch (error) {
// Invalid token, but don't throw - let resolvers handle auth
}
}
return {
user,
services: {
userService: new UserService(),
postService: new PostService()
}
};
}
})
);DataLoader for N+1 Prevention
import DataLoader from 'dataloader';
import { UserService } from '../services/user.service';
import { PostService } from '../services/post.service';
export function createLoaders() {
const userLoader = new DataLoader(async (ids: readonly string[]) => {
const users = await UserService.findByIds(Array.from(ids));
const userMap = new Map(users.map(user => [user.id, user]));
return ids.map(id => userMap.get(id) || null);
});
const postsByAuthorLoader = new DataLoader(async (authorIds: readonly string[]) => {
const posts = await PostService.findByAuthorIds(Array.from(authorIds));
// Group posts by author
const postsByAuthor = new Map<string, any[]>();
posts.forEach(post => {
const authorPosts = postsByAuthor.get(post.authorId) || [];
authorPosts.push(post);
postsByAuthor.set(post.authorId, authorPosts);
});
return authorIds.map(id => postsByAuthor.get(id) || []);
});
return {
userLoader,
postsByAuthorLoader
};
}
// Usage in context
app.use('/graphql', expressMiddleware(server, {
context: async ({ req }) => {
return {
user: await getUserFromToken(req),
loaders: createLoaders()
};
}
}));
// In resolver
User: {
posts: async (parent, _, context) => {
return context.loaders.postsByAuthorLoader.load(parent.id);
}
}gRPC Services
Protocol Buffer Definition
// user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (UserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (UserResponse);
rpc UpdateUser(UpdateUserRequest) returns (UserResponse);
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
// Server streaming
rpc StreamUsers(StreamUsersRequest) returns (stream UserResponse);
// Client streaming
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
// Bidirectional streaming
rpc ChatUsers(stream ChatMessage) returns (stream ChatMessage);
}
message User {
string id = 1;
string email = 2;
string name = 3;
Role role = 4;
int64 created_at = 5;
int64 updated_at = 6;
}
enum Role {
USER = 0;
ADMIN = 1;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
UserFilter filter = 3;
}
message UserFilter {
optional Role role = 1;
optional string search = 2;
}
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
int32 page = 3;
int32 total_pages = 4;
}
message CreateUserRequest {
string email = 1;
string name = 2;
string password = 3;
}
message UpdateUserRequest {
string id = 1;
optional string email = 2;
optional string name = 3;
}
message DeleteUserRequest {
string id = 1;
}
message DeleteUserResponse {
bool success = 1;
}
message UserResponse {
User user = 1;
}
message StreamUsersRequest {
UserFilter filter = 1;
}
message CreateUsersResponse {
repeated User users = 1;
int32 count = 2;
}
message ChatMessage {
string user_id = 1;
string message = 2;
int64 timestamp = 3;
}gRPC Server Implementation
// server.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { UserService } from './services/user.service';
const PROTO_PATH = './proto/user.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
const userService = new UserService();
// Unary RPC
async function getUser(
call: grpc.ServerUnaryCall<any, any>,
callback: grpc.sendUnaryData<any>
) {
try {
const user = await userService.getUserById(call.request.id);
callback(null, { user });
} catch (error) {
callback({
code: grpc.status.NOT_FOUND,
message: error.message
});
}
}
async function listUsers(
call: grpc.ServerUnaryCall<any, any>,
callback: grpc.sendUnaryData<any>
) {
try {
const { page = 1, limit = 10, filter } = call.request;
const result = await userService.listUsers(page, limit, filter);
callback(null, {
users: result.users,
total: result.total,
page,
total_pages: Math.ceil(result.total / limit)
});
} catch (error) {
callback({
code: grpc.status.INTERNAL,
message: error.message
});
}
}
// Server streaming RPC
function streamUsers(call: grpc.ServerWritableStream<any, any>) {
const { filter } = call.request;
userService.streamUsers(filter, (user) => {
call.write({ user });
})
.then(() => call.end())
.catch(error => {
call.emit('error', {
code: grpc.status.INTERNAL,
message: error.message
});
});
}
// Client streaming RPC
function createUsers(
call: grpc.ServerReadableStream<any, any>,
callback: grpc.sendUnaryData<any>
) {
const users: any[] = [];
call.on('data', async (request) => {
try {
const user = await userService.createUser(request);
users.push(user);
} catch (error) {
call.emit('error', {
code: grpc.status.INTERNAL,
message: error.message
});
}
});
call.on('end', () => {
callback(null, { users, count: users.length });
});
}
// Bidirectional streaming RPC
function chatUsers(call: grpc.ServerDuplexStream<any, any>) {
call.on('data', (message) => {
// Broadcast to all connected clients
call.write({
user_id: message.user_id,
message: message.message,
timestamp: Date.now()
});
});
call.on('end', () => {
call.end();
});
}
const server = new grpc.Server();
server.addService(userProto.UserService.service, {
getUser,
listUsers,
streamUsers,
createUsers,
chatUsers
});
const PORT = process.env.GRPC_PORT || 50051;
server.bindAsync(
`0.0.0.0:${PORT}`,
grpc.ServerCredentials.createInsecure(),
(error, port) => {
if (error) {
console.error('Failed to start gRPC server:', error);
return;
}
console.log(`gRPC server running on port ${port}`);
server.start();
}
);Rate Limiting
// Redis-based rate limiter
import Redis from 'ioredis';
import { Request, Response, NextFunction } from 'express';
export class RateLimiter {
private redis: Redis;
constructor() {
this.redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379')
});
}
async checkLimit(
key: string,
limit: number,
windowSeconds: number
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
const now = Date.now();
const windowStart = now - windowSeconds * 1000;
// Use sorted set to track requests
const pipeline = this.redis.pipeline();
// Remove old entries
pipeline.zremrangebyscore(key, 0, windowStart);
// Add current request
pipeline.zadd(key, now, `${now}`);
// Count requests in window
pipeline.zcount(key, windowStart, now);
// Set expiration
pipeline.expire(key, windowSeconds);
const results = await pipeline.exec();
const count = results?.[2]?.[1] as number;
const allowed = count <= limit;
const resetAt = now + windowSeconds * 1000;
return {
allowed,
remaining: Math.max(0, limit - count),
resetAt
};
}
middleware(options: {
limit: number;
windowSeconds: number;
keyGenerator?: (req: Request) => string;
}) {
return async (req: Request, res: Response, next: NextFunction) => {
const key = options.keyGenerator
? options.keyGenerator(req)
: `ratelimit:${req.ip}`;
const result = await this.checkLimit(
key,
options.limit,
options.windowSeconds
);
res.setHeader('X-RateLimit-Limit', options.limit);
res.setHeader('X-RateLimit-Remaining', result.remaining);
res.setHeader('X-RateLimit-Reset', result.resetAt);
if (!result.allowed) {
return res.status(429).json({
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests, please try again later'
}
});
}
next();
};
}
}
// Usage
const rateLimiter = new RateLimiter();
// Global rate limit
app.use(rateLimiter.middleware({
limit: 100,
windowSeconds: 60 // 100 requests per minute
}));
// Per-user rate limit
app.use('/api/', rateLimiter.middleware({
limit: 1000,
windowSeconds: 3600, // 1000 requests per hour
keyGenerator: (req) => `ratelimit:user:${req.user?.id || req.ip}`
}));
// Endpoint-specific rate limit
app.post('/api/auth/login',
rateLimiter.middleware({
limit: 5,
windowSeconds: 300 // 5 login attempts per 5 minutes
}),
loginHandler
);This reference provides comprehensive API design patterns and implementation techniques for modern backend development.
Authentication & Authorization
JWT Authentication
// Node.js JWT implementation
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { UserRepository } from '../repositories/user.repository';
import { ApiError } from '../utils/errors';
interface JwtPayload {
userId: string;
email: string;
role: string;
}
export class AuthService {
private readonly JWT_SECRET = process.env.JWT_SECRET!;
private readonly JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
private readonly REFRESH_TOKEN_EXPIRES_IN = '30d';
constructor(private userRepository: UserRepository) {}
generateAccessToken(payload: JwtPayload): string {
return jwt.sign(payload, this.JWT_SECRET, {
expiresIn: this.JWT_EXPIRES_IN
});
}
generateRefreshToken(userId: string): string {
return jwt.sign({ userId }, this.JWT_SECRET, {
expiresIn: this.REFRESH_TOKEN_EXPIRES_IN
});
}
verifyToken(token: string): JwtPayload {
try {
return jwt.verify(token, this.JWT_SECRET) as JwtPayload;
} catch (error) {
throw new ApiError(401, 'Invalid or expired token');
}
}
async login(email: string, password: string) {
const user = await this.userRepository.findByEmail(email);
if (!user || !(await this.verifyPassword(password, user.password))) {
throw new ApiError(401, 'Invalid credentials');
}
const payload: JwtPayload = {
userId: user.id,
email: user.email,
role: user.role
};
const accessToken = this.generateAccessToken(payload);
const refreshToken = this.generateRefreshToken(user.id);
return {
accessToken,
refreshToken,
user: { id: user.id, email: user.email, name: user.name, role: user.role }
};
}
async refreshAccessToken(refreshToken: string) {
const payload = this.verifyToken(refreshToken);
const user = await this.userRepository.findById(payload.userId);
if (!user) {
throw new ApiError(401, 'User not found');
}
const newPayload: JwtPayload = {
userId: user.id,
email: user.email,
role: user.role
};
return {
accessToken: this.generateAccessToken(newPayload)
};
}
}
// Middleware
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new ApiError(401, 'No token provided');
}
const token = authHeader.substring(7);
const authService = new AuthService(new UserRepository());
const payload = authService.verifyToken(token);
req.user = payload;
next();
} catch (error) {
next(error);
}
};
export const authorize = (roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return next(new ApiError(401, 'Authentication required'));
}
if (!roles.includes(req.user.role)) {
return next(new ApiError(403, 'Insufficient permissions'));
}
next();
};
};Caching Strategies
// Redis caching layer
import Redis from 'ioredis';
export class CacheService {
private redis: Redis;
private readonly DEFAULT_TTL = 3600; // 1 hour
constructor() {
this.redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD
});
}
async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: any, ttl = this.DEFAULT_TTL): Promise<void> {
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
async delPattern(pattern: string): Promise<void> {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
// Cache-aside pattern
async getOrSet<T>(
key: string,
factory: () => Promise<T>,
ttl = this.DEFAULT_TTL
): Promise<T> {
const cached = await this.get<T>(key);
if (cached) return cached;
const data = await factory();
await this.set(key, data, ttl);
return data;
}
}
// Usage with service
export class UserService {
constructor(
private userRepository: UserRepository,
private cacheService: CacheService
) {}
async getUserById(id: string): Promise<User> {
const cacheKey = `user:${id}`;
return this.cacheService.getOrSet(
cacheKey,
() => this.userRepository.findById(id),
3600 // 1 hour TTL
);
}
async updateUser(id: string, data: UpdateUserDto): Promise<User> {
const user = await this.userRepository.update(id, data);
// Invalidate cache
await this.cacheService.del(`user:${id}`);
return user;
}
}Database Patterns
Transaction Management
// TypeORM transactions
import { DataSource } from 'typeorm';
export class TransferService {
constructor(private dataSource: DataSource) {}
async transferMoney(fromAccountId: string, toAccountId: string, amount: number) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// Deduct from sender
await queryRunner.manager.decrement(
Account,
{ id: fromAccountId },
'balance',
amount
);
// Add to receiver
await queryRunner.manager.increment(
Account,
{ id: toAccountId },
'balance',
amount
);
// Create transaction record
const transaction = queryRunner.manager.create(Transaction, {
fromAccountId,
toAccountId,
amount,
status: 'completed'
});
await queryRunner.manager.save(transaction);
await queryRunner.commitTransaction();
return transaction;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
}
// Prisma transactions
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function transferMoney(fromId: string, toId: string, amount: number) {
return await prisma.$transaction(async (tx) => {
// Deduct from sender
const sender = await tx.account.update({
where: { id: fromId },
data: { balance: { decrement: amount } }
});
if (sender.balance < 0) {
throw new Error('Insufficient funds');
}
// Add to receiver
await tx.account.update({
where: { id: toId },
data: { balance: { increment: amount } }
});
// Create transaction record
return await tx.transaction.create({
data: {
fromAccountId: fromId,
toAccountId: toId,
amount,
status: 'completed'
}
});
});
}Microservices Architecture
This reference covers microservices patterns, service communication, API gateways, distributed tracing, and best practices for building scalable distributed systems.
Service Communication Patterns
Synchronous Communication (HTTP/REST)
// Service A calling Service B
import axios, { AxiosInstance } from 'axios';
import CircuitBreaker from 'opossum';
export class UserServiceClient {
private client: AxiosInstance;
private circuitBreaker: CircuitBreaker;
constructor(baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 5000,
headers: {
'Content-Type': 'application/json'
}
});
// Add request interceptor for tracing
this.client.interceptors.request.use((config) => {
// Add correlation ID for distributed tracing
config.headers['X-Correlation-ID'] =
config.headers['X-Correlation-ID'] || this.generateCorrelationId();
return config;
});
// Circuit breaker configuration
const options = {
timeout: 5000, // If function takes longer than 5s, trigger failure
errorThresholdPercentage: 50, // When 50% of requests fail, open circuit
resetTimeout: 30000 // After 30s, try again
};
this.circuitBreaker = new CircuitBreaker(
this.makeRequest.bind(this),
options
);
// Circuit breaker events
this.circuitBreaker.on('open', () => {
console.log('Circuit breaker opened - too many failures');
});
this.circuitBreaker.on('halfOpen', () => {
console.log('Circuit breaker half-open - trying request');
});
this.circuitBreaker.on('close', () => {
console.log('Circuit breaker closed - service recovered');
});
}
private generateCorrelationId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private async makeRequest<T>(
method: string,
url: string,
data?: any
): Promise<T> {
const response = await this.client.request<T>({
method,
url,
data
});
return response.data;
}
async getUser(userId: string): Promise<User> {
try {
return await this.circuitBreaker.fire('GET', `/users/${userId}`);
} catch (error) {
// Circuit is open, return cached data or default
console.error('Failed to fetch user', error);
throw error;
}
}
async createUser(userData: CreateUserDto): Promise<User> {
return this.circuitBreaker.fire('POST', '/users', userData);
}
async updateUser(userId: string, userData: UpdateUserDto): Promise<User> {
return this.circuitBreaker.fire('PUT', `/users/${userId}`, userData);
}
}
// Retry with exponential backoff
async function fetchWithRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay + jitter));
}
}
}
throw lastError!;
}gRPC Communication
// user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc CreateUser (CreateUserRequest) returns (User);
rpc UpdateUser (UpdateUserRequest) returns (User);
rpc DeleteUser (DeleteUserRequest) returns (Empty);
rpc ListUsers (ListUsersRequest) returns (stream User);
}
message User {
string id = 1;
string email = 2;
string name = 3;
string role = 4;
int64 created_at = 5;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string email = 1;
string name = 2;
string password = 3;
}
message UpdateUserRequest {
string id = 1;
optional string name = 2;
optional string email = 3;
}
message DeleteUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message Empty {}// gRPC server
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const PROTO_PATH = './proto/user.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
// Service implementation
const userService = {
async getUser(call: any, callback: any) {
try {
const user = await userRepository.findById(call.request.id);
if (!user) {
return callback({
code: grpc.status.NOT_FOUND,
message: 'User not found'
});
}
callback(null, user);
} catch (error) {
callback({
code: grpc.status.INTERNAL,
message: 'Internal server error'
});
}
},
async createUser(call: any, callback: any) {
try {
const user = await userRepository.create(call.request);
callback(null, user);
} catch (error) {
callback({
code: grpc.status.INVALID_ARGUMENT,
message: error.message
});
}
},
async listUsers(call: any) {
try {
const users = await userRepository.findAll({
page: call.request.page,
limit: call.request.limit
});
for (const user of users) {
call.write(user);
}
call.end();
} catch (error) {
call.destroy(new Error('Failed to list users'));
}
}
};
// Start server
const server = new grpc.Server();
server.addService(userProto.UserService.service, userService);
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
(error, port) => {
if (error) {
console.error('Failed to start server:', error);
return;
}
console.log(`gRPC server running on port ${port}`);
server.start();
}
);
// gRPC client
export class UserServiceGrpcClient {
private client: any;
constructor(address: string) {
this.client = new userProto.UserService(
address,
grpc.credentials.createInsecure()
);
}
async getUser(userId: string): Promise<User> {
return new Promise((resolve, reject) => {
this.client.getUser({ id: userId }, (error: any, response: any) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
async createUser(userData: CreateUserDto): Promise<User> {
return new Promise((resolve, reject) => {
this.client.createUser(userData, (error: any, response: any) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
listUsers(page: number, limit: number): AsyncIterable<User> {
const call = this.client.listUsers({ page, limit });
return {
[Symbol.asyncIterator]: async function* () {
for await (const user of call) {
yield user;
}
}
};
}
}Asynchronous Communication (Message Queues)
// RabbitMQ publisher
import amqp, { Connection, Channel } from 'amqplib';
export class MessageQueueService {
private connection: Connection | null = null;
private channel: Channel | null = null;
async connect(): Promise<void> {
this.connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://localhost');
this.channel = await this.connection.createChannel();
}
async publishToQueue(queue: string, message: any): Promise<void> {
if (!this.channel) {
await this.connect();
}
await this.channel!.assertQueue(queue, { durable: true });
this.channel!.sendToQueue(
queue,
Buffer.from(JSON.stringify(message)),
{ persistent: true }
);
}
async publishToExchange(
exchange: string,
routingKey: string,
message: any,
exchangeType: string = 'topic'
): Promise<void> {
if (!this.channel) {
await this.connect();
}
await this.channel!.assertExchange(exchange, exchangeType, { durable: true });
this.channel!.publish(
exchange,
routingKey,
Buffer.from(JSON.stringify(message)),
{ persistent: true }
);
}
async consumeQueue(
queue: string,
handler: (message: any) => Promise<void>
): Promise<void> {
if (!this.channel) {
await this.connect();
}
await this.channel!.assertQueue(queue, { durable: true });
await this.channel!.prefetch(1); // Process one message at a time
this.channel!.consume(queue, async (msg) => {
if (msg) {
try {
const content = JSON.parse(msg.content.toString());
await handler(content);
this.channel!.ack(msg);
} catch (error) {
console.error('Failed to process message:', error);
// Reject and requeue
this.channel!.nack(msg, false, true);
}
}
});
}
async close(): Promise<void> {
await this.channel?.close();
await this.connection?.close();
}
}
// Event-driven architecture
interface DomainEvent {
eventId: string;
eventType: string;
aggregateId: string;
timestamp: Date;
data: any;
}
export class EventPublisher {
constructor(private messageQueue: MessageQueueService) {}
async publish(event: DomainEvent): Promise<void> {
await this.messageQueue.publishToExchange(
'domain-events',
event.eventType,
event
);
}
}
export class EventSubscriber {
constructor(private messageQueue: MessageQueueService) {}
async subscribe(
eventTypes: string[],
handler: (event: DomainEvent) => Promise<void>
): Promise<void> {
const queueName = `subscriber-${Date.now()}`;
for (const eventType of eventTypes) {
await this.messageQueue.channel!.bindQueue(
queueName,
'domain-events',
eventType
);
}
await this.messageQueue.consumeQueue(queueName, handler);
}
}
// Usage
const messageQueue = new MessageQueueService();
await messageQueue.connect();
const eventPublisher = new EventPublisher(messageQueue);
// User service publishes event
await eventPublisher.publish({
eventId: '123',
eventType: 'user.created',
aggregateId: user.id,
timestamp: new Date(),
data: { email: user.email, name: user.name }
});
// Email service subscribes to user events
const eventSubscriber = new EventSubscriber(messageQueue);
await eventSubscriber.subscribe(['user.created'], async (event) => {
console.log('Received event:', event);
// Send welcome email
await emailService.sendWelcomeEmail(event.data.email);
});API Gateway
// Express-based API Gateway
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
const app = express();
// Security
app.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per window
message: 'Too many requests from this IP'
});
app.use('/api', limiter);
// Authentication middleware
app.use('/api', async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const user = await verifyToken(token);
req.user = user;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
});
// Service routing with load balancing
const userServiceTargets = [
'http://user-service-1:3001',
'http://user-service-2:3001',
'http://user-service-3:3001'
];
let userServiceIndex = 0;
app.use('/api/users', createProxyMiddleware({
target: userServiceTargets[0],
changeOrigin: true,
pathRewrite: {
'^/api/users': '/users'
},
router: (req) => {
// Round-robin load balancing
const target = userServiceTargets[userServiceIndex];
userServiceIndex = (userServiceIndex + 1) % userServiceTargets.length;
return target;
},
onProxyReq: (proxyReq, req, res) => {
// Forward user information
if (req.user) {
proxyReq.setHeader('X-User-ID', req.user.id);
proxyReq.setHeader('X-User-Role', req.user.role);
}
// Add correlation ID for tracing
const correlationId = req.headers['x-correlation-id'] || generateId();
proxyReq.setHeader('X-Correlation-ID', correlationId);
},
onError: (err, req, res) => {
console.error('Proxy error:', err);
res.status(503).json({ error: 'Service unavailable' });
}
}));
app.use('/api/posts', createProxyMiddleware({
target: 'http://post-service:3002',
changeOrigin: true,
pathRewrite: {
'^/api/posts': '/posts'
}
}));
// Request aggregation
app.get('/api/user-dashboard/:userId', async (req, res) => {
const { userId } = req.params;
try {
// Fetch data from multiple services in parallel
const [user, posts, comments] = await Promise.all([
userServiceClient.getUser(userId),
postServiceClient.getUserPosts(userId),
commentServiceClient.getUserComments(userId)
]);
res.json({
user,
posts,
comments
});
} catch (error) {
res.status(500).json({ error: 'Failed to fetch dashboard data' });
}
});
// Health check aggregation
app.get('/health', async (req, res) => {
const services = [
{ name: 'user-service', url: 'http://user-service:3001/health' },
{ name: 'post-service', url: 'http://post-service:3002/health' },
{ name: 'comment-service', url: 'http://comment-service:3003/health' }
];
const results = await Promise.allSettled(
services.map(async (service) => {
const response = await axios.get(service.url, { timeout: 2000 });
return { name: service.name, status: 'healthy', data: response.data };
})
);
const health = results.map((result, index) => {
if (result.status === 'fulfilled') {
return result.value;
} else {
return {
name: services[index].name,
status: 'unhealthy',
error: result.reason.message
};
}
});
const allHealthy = health.every(h => h.status === 'healthy');
const statusCode = allHealthy ? 200 : 503;
res.status(statusCode).json({ services: health });
});
app.listen(3000, () => {
console.log('API Gateway running on port 3000');
});Service Discovery
// Consul-based service discovery
import Consul from 'consul';
export class ServiceRegistry {
private consul: Consul.Consul;
constructor() {
this.consul = new Consul({
host: process.env.CONSUL_HOST || 'localhost',
port: process.env.CONSUL_PORT || '8500'
});
}
async registerService(
serviceName: string,
serviceId: string,
port: number,
health: string = '/health'
): Promise<void> {
await this.consul.agent.service.register({
id: serviceId,
name: serviceName,
address: process.env.SERVICE_HOST || 'localhost',
port,
check: {
http: `http://${process.env.SERVICE_HOST}:${port}${health}`,
interval: '10s',
timeout: '5s'
},
tags: [process.env.NODE_ENV || 'development']
});
console.log(`Service ${serviceName} registered with ID ${serviceId}`);
}
async deregisterService(serviceId: string): Promise<void> {
await this.consul.agent.service.deregister(serviceId);
console.log(`Service ${serviceId} deregistered`);
}
async discoverService(serviceName: string): Promise<string[]> {
const result = await this.consul.health.service({
service: serviceName,
passing: true
});
return result.map((entry: any) => {
const { Address, Port } = entry.Service;
return `http://${Address}:${Port}`;
});
}
async getServiceInstance(serviceName: string): Promise<string> {
const instances = await this.discoverService(serviceName);
if (instances.length === 0) {
throw new Error(`No healthy instances of ${serviceName} found`);
}
// Random selection (simple load balancing)
const index = Math.floor(Math.random() * instances.length);
return instances[index];
}
}
// Usage in service
const registry = new ServiceRegistry();
const serviceId = `user-service-${process.env.HOSTNAME || 'local'}`;
await registry.registerService('user-service', serviceId, 3001);
// Graceful shutdown
process.on('SIGTERM', async () => {
await registry.deregisterService(serviceId);
process.exit(0);
});
// Client with service discovery
export class DiscoverableHttpClient {
constructor(
private serviceName: string,
private registry: ServiceRegistry
) {}
async request<T>(
method: string,
path: string,
data?: any
): Promise<T> {
const serviceUrl = await this.registry.getServiceInstance(this.serviceName);
const response = await axios.request<T>({
method,
url: `${serviceUrl}${path}`,
data,
timeout: 5000
});
return response.data;
}
}
const userClient = new DiscoverableHttpClient('user-service', registry);
const user = await userClient.request('GET', '/users/123');Distributed Tracing
// OpenTelemetry setup
import { NodeSDK } from '@opentelemetry/sdk-node';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
// Initialize tracing
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'user-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0'
}),
traceExporter: new JaegerExporter({
endpoint: process.env.JAEGER_ENDPOINT || 'http://localhost:14268/api/traces'
}),
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation()
]
});
sdk.start();
// Graceful shutdown
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
// Custom spans
export class UserService {
private tracer = trace.getTracer('user-service');
async createUser(userData: CreateUserDto): Promise<User> {
const span = this.tracer.startSpan('createUser');
try {
// Add attributes to span
span.setAttribute('user.email', userData.email);
span.setAttribute('user.role', userData.role || 'user');
// Validate data
const validationSpan = this.tracer.startSpan('validateUser', {
parent: span
});
await this.validateUserData(userData);
validationSpan.end();
// Save to database
const dbSpan = this.tracer.startSpan('saveUser', {
parent: span
});
const user = await this.userRepository.create(userData);
dbSpan.setAttribute('db.operation', 'insert');
dbSpan.setAttribute('db.table', 'users');
dbSpan.end();
// Publish event
const eventSpan = this.tracer.startSpan('publishUserCreatedEvent', {
parent: span
});
await this.eventPublisher.publish({
eventType: 'user.created',
aggregateId: user.id,
data: user
});
eventSpan.end();
span.setStatus({ code: SpanStatusCode.OK });
return user;
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message
});
throw error;
} finally {
span.end();
}
}
async getUserWithPosts(userId: string): Promise<UserWithPosts> {
const span = this.tracer.startSpan('getUserWithPosts');
span.setAttribute('user.id', userId);
try {
// Fetch user
const userSpan = this.tracer.startSpan('fetchUser', { parent: span });
const user = await this.userRepository.findById(userId);
userSpan.end();
if (!user) {
throw new NotFoundError('User not found');
}
// Fetch posts from another service
const postsSpan = this.tracer.startSpan('fetchUserPosts', { parent: span });
const posts = await this.postServiceClient.getUserPosts(userId);
postsSpan.setAttribute('posts.count', posts.length);
postsSpan.end();
span.setStatus({ code: SpanStatusCode.OK });
return { ...user, posts };
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message
});
throw error;
} finally {
span.end();
}
}
}
// Middleware to extract trace context
export function tracingMiddleware(req: Request, res: Response, next: NextFunction) {
// Extract correlation ID from headers
const correlationId = req.headers['x-correlation-id'] as string || generateId();
// Add to request for later use
req.correlationId = correlationId;
// Add to response headers
res.setHeader('X-Correlation-ID', correlationId);
// Add to active span
const span = trace.getActiveSpan();
if (span) {
span.setAttribute('correlation.id', correlationId);
span.setAttribute('http.method', req.method);
span.setAttribute('http.url', req.url);
}
next();
}Saga Pattern (Distributed Transactions)
// Orchestration-based saga
interface SagaStep {
execute: () => Promise<any>;
compensate: () => Promise<void>;
}
export class SagaOrchestrator {
private steps: SagaStep[] = [];
private executedSteps: SagaStep[] = [];
addStep(step: SagaStep): void {
this.steps.push(step);
}
async execute(): Promise<void> {
try {
for (const step of this.steps) {
const result = await step.execute();
this.executedSteps.push(step);
console.log('Step executed successfully');
}
} catch (error) {
console.error('Saga failed, starting compensation', error);
await this.compensate();
throw error;
}
}
private async compensate(): Promise<void> {
// Compensate in reverse order
for (const step of this.executedSteps.reverse()) {
try {
await step.compensate();
console.log('Step compensated');
} catch (error) {
console.error('Compensation failed', error);
// Continue compensating other steps
}
}
}
}
// Example: Order creation saga
export class OrderCreationSaga {
constructor(
private orderService: OrderService,
private inventoryService: InventoryService,
private paymentService: PaymentService,
private notificationService: NotificationService
) {}
async execute(orderData: CreateOrderDto): Promise<Order> {
const saga = new SagaOrchestrator();
let orderId: string;
let reservationId: string;
let paymentId: string;
// Step 1: Create order
saga.addStep({
execute: async () => {
const order = await this.orderService.createOrder(orderData);
orderId = order.id;
return order;
},
compensate: async () => {
await this.orderService.cancelOrder(orderId);
}
});
// Step 2: Reserve inventory
saga.addStep({
execute: async () => {
const reservation = await this.inventoryService.reserveItems(
orderData.items
);
reservationId = reservation.id;
return reservation;
},
compensate: async () => {
await this.inventoryService.releaseReservation(reservationId);
}
});
// Step 3: Process payment
saga.addStep({
execute: async () => {
const payment = await this.paymentService.processPayment({
orderId,
amount: orderData.totalAmount,
customerId: orderData.customerId
});
paymentId = payment.id;
return payment;
},
compensate: async () => {
await this.paymentService.refund(paymentId);
}
});
// Step 4: Send notification
saga.addStep({
execute: async () => {
await this.notificationService.sendOrderConfirmation(orderId);
},
compensate: async () => {
// Notification doesn't need compensation
}
});
await saga.execute();
const order = await this.orderService.getOrder(orderId);
return order;
}
}
// Choreography-based saga (event-driven)
export class OrderService {
constructor(private eventPublisher: EventPublisher) {}
async createOrder(orderData: CreateOrderDto): Promise<Order> {
const order = await this.orderRepository.create({
...orderData,
status: 'pending'
});
// Publish event
await this.eventPublisher.publish({
eventType: 'order.created',
aggregateId: order.id,
data: order
});
return order;
}
async handleInventoryReserved(event: DomainEvent): Promise<void> {
const { orderId } = event.data;
await this.orderRepository.update(orderId, {
status: 'inventory_reserved'
});
// Request payment
await this.eventPublisher.publish({
eventType: 'payment.requested',
aggregateId: orderId,
data: { orderId, amount: event.data.amount }
});
}
async handlePaymentProcessed(event: DomainEvent): Promise<void> {
const { orderId } = event.data;
await this.orderRepository.update(orderId, {
status: 'completed',
paymentId: event.data.paymentId
});
// Send confirmation
await this.eventPublisher.publish({
eventType: 'order.completed',
aggregateId: orderId,
data: { orderId }
});
}
async handlePaymentFailed(event: DomainEvent): Promise<void> {
const { orderId } = event.data;
// Release inventory
await this.eventPublisher.publish({
eventType: 'inventory.release_requested',
aggregateId: orderId,
data: { orderId }
});
await this.orderRepository.update(orderId, {
status: 'cancelled',
cancellationReason: 'payment_failed'
});
}
}Docker Compose for Microservices
version: '3.8'
services:
# API Gateway
api-gateway:
build: ./api-gateway
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- USER_SERVICE_URL=http://user-service:3001
- POST_SERVICE_URL=http://post-service:3002
depends_on:
- user-service
- post-service
networks:
- microservices
# User Service
user-service:
build: ./user-service
environment:
- NODE_ENV=production
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=users_db
- DB_USER=postgres
- DB_PASSWORD=postgres
- REDIS_HOST=redis
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- postgres
- redis
- rabbitmq
deploy:
replicas: 3
networks:
- microservices
# Post Service
post-service:
build: ./post-service
environment:
- NODE_ENV=production
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=posts_db
- REDIS_HOST=redis
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- postgres
- redis
- rabbitmq
deploy:
replicas: 2
networks:
- microservices
# PostgreSQL
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
networks:
- microservices
# Redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- microservices
# RabbitMQ
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "5672:5672"
- "15672:15672"
environment:
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- microservices
# Consul (Service Discovery)
consul:
image: consul:latest
ports:
- "8500:8500"
networks:
- microservices
# Jaeger (Distributed Tracing)
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "14268:14268"
- "14250:14250"
- "9411:9411"
networks:
- microservices
volumes:
postgres-data:
redis-data:
rabbitmq-data:
networks:
microservices:
driver: bridgeThis reference provides comprehensive patterns and best practices for building microservices architectures with proper communication, resilience, and observability.
Node.js Development
Express.js REST API
Modern Express API with TypeScript:
// src/app.ts
import express, { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';
import rateLimit from 'express-rate-limit';
import { userRouter } from './routes/users';
import { errorHandler } from './middleware/errorHandler';
import { requestLogger } from './middleware/logger';
import { authenticate } from './middleware/auth';
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api/', limiter);
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(compression());
// Request logging
app.use(requestLogger);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Routes
app.use('/api/users', authenticate, userRouter);
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Error handler (must be last)
app.use(errorHandler);
export default app;
// src/server.ts
import app from './app';
import { connectDatabase } from './config/database';
import { logger } from './utils/logger';
const PORT = process.env.PORT || 3000;
async function startServer() {
try {
await connectDatabase();
logger.info('Database connected successfully');
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
});
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
}
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('SIGTERM received, shutting down gracefully');
process.exit(0);
});
startServer();Controllers and Services Pattern:
// src/types/user.ts
export interface User {
id: string;
email: string;
name: string;
role: 'user' | 'admin';
createdAt: Date;
updatedAt: Date;
}
export interface CreateUserDto {
email: string;
name: string;
password: string;
}
export interface UpdateUserDto {
name?: string;
email?: string;
}
// src/services/user.service.ts
import { User, CreateUserDto, UpdateUserDto } from '../types/user';
import { UserRepository } from '../repositories/user.repository';
import { hashPassword } from '../utils/crypto';
import { ApiError } from '../utils/errors';
export class UserService {
constructor(private userRepository: UserRepository) {}
async createUser(data: CreateUserDto): Promise<User> {
// Check if user exists
const existing = await this.userRepository.findByEmail(data.email);
if (existing) {
throw new ApiError(409, 'User with this email already exists');
}
// Hash password
const hashedPassword = await hashPassword(data.password);
// Create user
const user = await this.userRepository.create({
...data,
password: hashedPassword
});
return user;
}
async getUserById(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new ApiError(404, 'User not found');
}
return user;
}
async updateUser(id: string, data: UpdateUserDto): Promise<User> {
const user = await this.getUserById(id);
// Check email uniqueness if changing
if (data.email && data.email !== user.email) {
const existing = await this.userRepository.findByEmail(data.email);
if (existing) {
throw new ApiError(409, 'Email already in use');
}
}
const updated = await this.userRepository.update(id, data);
return updated;
}
async deleteUser(id: string): Promise<void> {
await this.getUserById(id); // Ensure exists
await this.userRepository.delete(id);
}
async listUsers(page = 1, limit = 10): Promise<{ users: User[]; total: number }> {
const offset = (page - 1) * limit;
const [users, total] = await Promise.all([
this.userRepository.findMany({ offset, limit }),
this.userRepository.count()
]);
return { users, total };
}
}
// src/controllers/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/user.service';
import { validateCreateUser, validateUpdateUser } from '../validators/user';
export class UserController {
constructor(private userService: UserService) {}
createUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const validation = validateCreateUser(req.body);
if (!validation.success) {
return res.status(400).json({ errors: validation.errors });
}
const user = await this.userService.createUser(validation.data);
res.status(201).json({ data: user });
} catch (error) {
next(error);
}
};
getUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const user = await this.userService.getUserById(req.params.id);
res.json({ data: user });
} catch (error) {
next(error);
}
};
updateUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const validation = validateUpdateUser(req.body);
if (!validation.success) {
return res.status(400).json({ errors: validation.errors });
}
const user = await this.userService.updateUser(req.params.id, validation.data);
res.json({ data: user });
} catch (error) {
next(error);
}
};
deleteUser = async (req: Request, res: Response, next: NextFunction) => {
try {
await this.userService.deleteUser(req.params.id);
res.status(204).send();
} catch (error) {
next(error);
}
};
listUsers = async (req: Request, res: Response, next: NextFunction) => {
try {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const result = await this.userService.listUsers(page, limit);
res.json({
data: result.users,
meta: {
page,
limit,
total: result.total,
totalPages: Math.ceil(result.total / limit)
}
});
} catch (error) {
next(error);
}
};
}
// src/routes/users.ts
import { Router } from 'express';
import { UserController } from '../controllers/user.controller';
import { UserService } from '../services/user.service';
import { UserRepository } from '../repositories/user.repository';
import { authorize } from '../middleware/auth';
const router = Router();
const userRepository = new UserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);
router.post('/', userController.createUser);
router.get('/', userController.listUsers);
router.get('/:id', userController.getUser);
router.patch('/:id', userController.updateUser);
router.delete('/:id', authorize(['admin']), userController.deleteUser);
export { router as userRouter };Repository Pattern with TypeORM:
// src/entities/User.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users')
export class UserEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column()
name: string;
@Column()
password: string;
@Column({ type: 'enum', enum: ['user', 'admin'], default: 'user' })
role: 'user' | 'admin';
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// src/repositories/user.repository.ts
import { Repository } from 'typeorm';
import { AppDataSource } from '../config/database';
import { UserEntity } from '../entities/User.entity';
import { User, CreateUserDto, UpdateUserDto } from '../types/user';
export class UserRepository {
private repository: Repository<UserEntity>;
constructor() {
this.repository = AppDataSource.getRepository(UserEntity);
}
async create(data: CreateUserDto & { password: string }): Promise<User> {
const user = this.repository.create(data);
await this.repository.save(user);
return this.toModel(user);
}
async findById(id: string): Promise<User | null> {
const user = await this.repository.findOne({ where: { id } });
return user ? this.toModel(user) : null;
}
async findByEmail(email: string): Promise<User | null> {
const user = await this.repository.findOne({ where: { email } });
return user ? this.toModel(user) : null;
}
async findMany(options: { offset: number; limit: number }): Promise<User[]> {
const users = await this.repository.find({
skip: options.offset,
take: options.limit,
order: { createdAt: 'DESC' }
});
return users.map(this.toModel);
}
async count(): Promise<number> {
return this.repository.count();
}
async update(id: string, data: UpdateUserDto): Promise<User> {
await this.repository.update(id, data);
const user = await this.findById(id);
if (!user) throw new Error('User not found after update');
return user;
}
async delete(id: string): Promise<void> {
await this.repository.delete(id);
}
private toModel(entity: UserEntity): User {
const { password, ...user } = entity;
return user as User;
}
}NestJS Application
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService]
})
export class UsersModule {}
// src/users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const existing = await this.usersRepository.findOne({
where: { email: createUserDto.email }
});
if (existing) {
throw new ConflictException('User with this email already exists');
}
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
const user = this.usersRepository.create({
...createUserDto,
password: hashedPassword
});
return this.usersRepository.save(user);
}
async findAll(page = 1, limit = 10): Promise<{ data: User[]; total: number }> {
const [data, total] = await this.usersRepository.findAndCount({
skip: (page - 1) * limit,
take: limit,
order: { createdAt: 'DESC' }
});
return { data, total };
}
async findOne(id: string): Promise<User> {
const user = await this.usersRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findOne(id);
if (updateUserDto.email && updateUserDto.email !== user.email) {
const existing = await this.usersRepository.findOne({
where: { email: updateUserDto.email }
});
if (existing) {
throw new ConflictException('Email already in use');
}
}
Object.assign(user, updateUserDto);
return this.usersRepository.save(user);
}
async remove(id: string): Promise<void> {
const result = await this.usersRepository.delete(id);
if (result.affected === 0) {
throw new NotFoundException(`User with ID ${id} not found`);
}
}
}
// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
UseGuards,
ParseIntPipe,
ParseUUIDPipe
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@Roles('admin')
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
async findAll(
@Query('page', ParseIntPipe) page = 1,
@Query('limit', ParseIntPipe) limit = 10
) {
const { data, total } = await this.usersService.findAll(page, limit);
return {
data,
meta: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}
@Patch(':id')
update(
@Param('id', ParseUUIDPipe) id: string,
@Body() updateUserDto: UpdateUserDto
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@Roles('admin')
remove(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.remove(id);
}
}Python Development
FastAPI Application
# main.py
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from contextlib import asynccontextmanager
import logging
from app.config import settings
from app.database import engine, Base
from app.routers import users, auth
from app.middleware.logging import LoggingMiddleware
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Rate limiter
limiter = Limiter(key_func=get_remote_address)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
logger.info("Starting application...")
Base.metadata.create_all(bind=engine)
yield
# Shutdown
logger.info("Shutting down application...")
app = FastAPI(
title="User Management API",
version="1.0.0",
lifespan=lifespan
)
# Middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(LoggingMiddleware)
# Rate limiting
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# Routes
@app.get("/health")
async def health_check():
return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(users.router, prefix="/api/users", tags=["users"])
# app/models/user.py
from sqlalchemy import Column, String, DateTime, Enum
from sqlalchemy.dialects.postgresql import UUID
from datetime import datetime
import uuid
import enum
from app.database import Base
class UserRole(str, enum.Enum):
USER = "user"
ADMIN = "admin"
class User(Base):
__tablename__ = "users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String, unique=True, index=True, nullable=False)
name = Column(String, nullable=False)
hashed_password = Column(String, nullable=False)
role = Column(Enum(UserRole), default=UserRole.USER)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# app/schemas/user.py
from pydantic import BaseModel, EmailStr, constr
from datetime import datetime
from uuid import UUID
from typing import Optional
class UserBase(BaseModel):
email: EmailStr
name: constr(min_length=1, max_length=100)
class UserCreate(UserBase):
password: constr(min_length=8, max_length=100)
class UserUpdate(BaseModel):
name: Optional[constr(min_length=1, max_length=100)] = None
email: Optional[EmailStr] = None
class UserResponse(UserBase):
id: UUID
role: str
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class UserListResponse(BaseModel):
data: list[UserResponse]
meta: dict
# app/services/user_service.py
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException, status
from passlib.context import CryptContext
from uuid import UUID
from app.models.user import User
from app.schemas.user import UserCreate, UserUpdate
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class UserService:
@staticmethod
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
@staticmethod
async def create_user(db: Session, user_data: UserCreate) -> User:
# Check if user exists
existing = db.query(User).filter(User.email == user_data.email).first()
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User with this email already exists"
)
hashed_password = UserService.get_password_hash(user_data.password)
db_user = User(
email=user_data.email,
name=user_data.name,
hashed_password=hashed_password
)
try:
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
except IntegrityError:
db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User with this email already exists"
)
@staticmethod
async def get_user(db: Session, user_id: UUID) -> User:
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
return user
@staticmethod
async def get_users(
db: Session,
page: int = 1,
limit: int = 10
) -> tuple[list[User], int]:
offset = (page - 1) * limit
users = db.query(User).offset(offset).limit(limit).all()
total = db.query(User).count()
return users, total
@staticmethod
async def update_user(
db: Session,
user_id: UUID,
user_data: UserUpdate
) -> User:
user = await UserService.get_user(db, user_id)
if user_data.email and user_data.email != user.email:
existing = db.query(User).filter(User.email == user_data.email).first()
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Email already in use"
)
for field, value in user_data.model_dump(exclude_unset=True).items():
setattr(user, field, value)
db.commit()
db.refresh(user)
return user
@staticmethod
async def delete_user(db: Session, user_id: UUID) -> None:
user = await UserService.get_user(db, user_id)
db.delete(user)
db.commit()
# app/routers/users.py
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from uuid import UUID
import math
from app.database import get_db
from app.schemas.user import UserCreate, UserUpdate, UserResponse, UserListResponse
from app.services.user_service import UserService
from app.dependencies.auth import get_current_user, require_admin
router = APIRouter()
@router.post("/", response_model=UserResponse, status_code=201)
async def create_user(
user_data: UserCreate,
db: Session = Depends(get_db)
):
return await UserService.create_user(db, user_data)
@router.get("/", response_model=UserListResponse)
async def list_users(
page: int = Query(1, ge=1),
limit: int = Query(10, ge=1, le=100),
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
users, total = await UserService.get_users(db, page, limit)
return UserListResponse(
data=[UserResponse.from_orm(user) for user in users],
meta={
"page": page,
"limit": limit,
"total": total,
"total_pages": math.ceil(total / limit)
}
)
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: UUID,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
return await UserService.get_user(db, user_id)
@router.patch("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: UUID,
user_data: UserUpdate,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
return await UserService.update_user(db, user_id, user_data)
@router.delete("/{user_id}", status_code=204)
async def delete_user(
user_id: UUID,
db: Session = Depends(get_db),
current_user = Depends(require_admin)
):
await UserService.delete_user(db, user_id)Related skills
FAQ
Which API styles are covered?
REST is primary; GraphQL and gRPC are referenced via linked guides.
Does it include database guidance?
Yes; repository patterns and ORM examples are part of the workflow.
What stacks are mentioned?
Node.js, Python, Java, and Go with common frameworks in the description.