본문으로 건너뛰기
cccskills
무료GitHub에 공개

nodejs-backend-patterns

Build production-ready Node.js backend services with Express/Fastify, implementing middleware patterns, error handling, authentication, database integration, and API design best practices. Use when creating Node.js servers, REST APIs, GraphQL backends, or microservices architectures.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works스타 182(GitHub 182 + cccskills 0)
연동 도구Redis

실행 점수 미검증

설치 방법 보기

포함된 파일(2)

  • SKILL.md15.8 KB
  • references/advanced-patterns.md8.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Node.js Backend Patterns

Comprehensive guidance for building scalable, maintainable, and production-ready Node.js backend applications with modern frameworks, architectural patterns, and best practices.

When to Use This Skill

  • Building REST APIs or GraphQL servers
  • Creating microservices with Node.js
  • Implementing authentication and authorization
  • Designing scalable backend architectures
  • Setting up middleware and error handling
  • Integrating databases (SQL and NoSQL)
  • Building real-time applications with WebSockets
  • Implementing background job processing

Core Frameworks

Express.js - Minimalist Framework

Basic Setup:

import express, { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';

const app = express();

// Security middleware
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
app.use(compression());

// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// Request logging
app.use((req: Request, res: Response, next: NextFunction) => {
	console.log(`${req.method} ${req.path}`);
	next();
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
	console.log(`Server running on port ${PORT}`);
});

Fastify - High Performance Framework

Basic Setup:

import Fastify from 'fastify';
import helmet from '@fastify/helmet';
import cors from '@fastify/cors';
import compress from '@fastify/compress';

const fastify = Fastify({
	logger: {
		level: process.env.LOG_LEVEL || 'info',
		transport: {
			target: 'pino-pretty',
			options: { colorize: true }
		}
	}
});

// Plugins
await fastify.register(helmet);
await fastify.register(cors, { origin: true });
await fastify.register(compress);

// Type-safe routes with schema validation
fastify.post<{
	Body: { name: string; email: string };
	Reply: { id: string; name: string };
}>(
	'/users',
	{
		schema: {
			body: {
				type: 'object',
				required: ['name', 'email'],
				properties: {
					name: { type: 'string', minLength: 1 },
					email: { type: 'string', format: 'email' }
				}
			}
		}
	},
	async (request, reply) => {
		const { name, email } = request.body;
		return { id: '123', name };
	}
);

await fastify.listen({ port: 3000, host: '0.0.0.0' });

Architectural Patterns

Pattern 1: Layered Architecture

Structure:

src/
├── controllers/     # Handle HTTP requests/responses
├── services/        # Business logic
├── repositories/    # Data access layer
├── models/          # Data models
├── middleware/      # Express/Fastify middleware
├── routes/          # Route definitions
├── utils/           # Helper functions
├── config/          # Configuration
└── types/           # TypeScript types

Controller Layer:

// controllers/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/user.service';
import { CreateUserDTO, UpdateUserDTO } from '../types/user.types';

export class UserController {
	constructor(private userService: UserService) {}

	async createUser(req: Request, res: Response, next: NextFunction) {
		try {
			const userData: CreateUserDTO = req.body;
			const user = await this.userService.createUser(userData);
			res.status(201).json(user);
		} catch (error) {
			next(error);
		}
	}

	async getUser(req: Request, res: Response, next: NextFunction) {
		try {
			const { id } = req.params;
			const user = await this.userService.getUserById(id);
			res.json(user);
		} catch (error) {
			next(error);
		}
	}

	async updateUser(req: Request, res: Response, next: NextFunction) {
		try {
			const { id } = req.params;
			const updates: UpdateUserDTO = req.body;
			const user = await this.userService.updateUser(id, updates);
			res.json(user);
		} catch (error) {
			next(error);
		}
	}

	async deleteUser(req: Request, res: Response, next: NextFunction) {
		try {
			const { id } = req.params;
			await this.userService.deleteUser(id);
			res.status(204).send();
		} catch (error) {
			next(error);
		}
	}
}

Service Layer:

// services/user.service.ts
import { UserRepository } from '../repositories/user.repository';
import { CreateUserDTO, UpdateUserDTO, User } from '../types/user.types';
import { NotFoundError, ValidationError } from '../utils/errors';
import bcrypt from 'bcrypt';

export class UserService {
	constructor(private userRepository: UserRepository) {}

	async createUser(userData: CreateUserDTO): Promise<User> {
		// Validation
		const existingUser = await this.userRepository.findByEmail(userData.email);
		if (existingUser) {
			throw new ValidationError('Email already exists');
		}

		// Hash password
		const hashedPassword = await bcrypt.hash(userData.password, 10);

		// Create user
		const user = await this.userRepository.create({
			...userData,
			password: hashedPassword
		});

		// Remove password from response
		const { password, ...userWithoutPassword } = user;
		return userWithoutPassword as User;
	}

	async getUserById(id: string): Promise<User> {
		const user = await this.userRepository.findById(id);
		if (!user) {
			throw new NotFoundError('User not found');
		}
		const { password, ...userWithoutPassword } = user;
		return userWithoutPassword as User;
	}

	async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {
		const user = await this.userRepository.update(id, updates);
		if (!user) {
			throw new NotFoundError('User not found');
		}
		const { password, ...userWithoutPassword } = user;
		return userWithoutPassword as User;
	}

	async deleteUser(id: string): Promise<void> {
		const deleted = await this.userRepository.delete(id);
		if (!deleted) {
			throw new NotFoundError('User not found');
		}
	}
}

Repository Layer:

// repositories/user.repository.ts
import { Pool } from 'pg';
import { CreateUserDTO, UpdateUserDTO, UserEntity } from '../types/user.types';

export class UserRepository {
	constructor(private db: Pool) {}

	async create(userData: CreateUserDTO & { password: string }): Promise<UserEntity> {
		const query = `
      INSERT INTO users (name, email, password)
      VALUES ($1, $2, $3)
      RETURNING id, name, email, password, created_at, updated_at
    `;
		const { rows } = await this.db.query(query, [userData.name, userData.email, userData.password]);
		return rows[0];
	}

	async findById(id: string): Promise<UserEntity | null> {
		const query = 'SELECT * FROM users WHERE id = $1';
		const { rows } = await this.db.query(query, [id]);
		return rows[0] || null;
	}

	async findByEmail(email: string): Promise<UserEntity | null> {
		const query = 'SELECT * FROM users WHERE email = $1';
		const { rows } = await this.db.query(query, [email]);
		return rows[0] || null;
	}

	async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> {
		const fields = Object.keys(updates);
		const values = Object.values(updates);

		const setClause = fields.map((field, idx) => `${field} = $${idx + 2}`).join(', ');

		const query = `
      UPDATE users
      SET ${setClause}, updated_at = CURRENT_TIMESTAMP
      WHERE id = $1
      RETURNING *
    `;

		const { rows } = await this.db.query(query, [id, ...values]);
		return rows[0] || null;
	}

	async delete(id: string): Promise<boolean> {
		const query = 'DELETE FROM users WHERE id = $1';
		const { rowCount } = await this.db.query(query, [id]);
		return rowCount > 0;
	}
}

Pattern 2: Dependency Injection

Use a DI container to wire up repositories, services, and controllers. For a full container implementation, see references/advanced-patterns.md.

Middleware Patterns

Authentication Middleware

// middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { UnauthorizedError } from '../utils/errors';

interface JWTPayload {
	userId: string;
	email: string;
}

declare global {
	namespace Express {
		interface Request {
			user?: JWTPayload;
		}
	}
}

export const authenticate = async (req: Request, res: Response, next: NextFunction) => {
	try {
		const token = req.headers.authorization?.replace('Bearer ', '');

		if (!token) {
			throw new UnauthorizedError('No token provided');
		}

		const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;

		req.user = payload;
		next();
	} catch (error) {
		next(new UnauthorizedError('Invalid token'));
	}
};

export const authorize = (...roles: string[]) => {
	return async (req: Request, res: Response, next: NextFunction) => {
		if (!req.user) {
			return next(new UnauthorizedError('Not authenticated'));
		}

		// Check if user has required role
		const hasRole = roles.some((role) => req.user?.roles?.includes(role));

		if (!hasRole) {
			return next(new UnauthorizedError('Insufficient permissions'));
		}

		next();
	};
};

Validation Middleware

// middleware/validation.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AnyZodObject, ZodError } from 'zod';
import { ValidationError } from '../utils/errors';

export const validate = (schema: AnyZodObject) => {
	return async (req: Request, res: Response, next: NextFunction) => {
		try {
			await schema.parseAsync({
				body: req.body,
				query: req.query,
				params: req.params
			});
			next();
		} catch (error) {
			if (error instanceof ZodError) {
				const errors = error.errors.map((err) => ({
					field: err.path.join('.'),
					message: err.message
				}));
				next(new ValidationError('Validation failed', errors));
			} else {
				next(error);
			}
		}
	};
};

// Usage with Zod
import { z } from 'zod';

const createUserSchema = z.object({
	body: z.object({
		name: z.string().min(1),
		email: z.string().email(),
		password: z.string().min(8)
	})
});

router.post('/users', validate(createUserSchema), userController.createUser);

Rate Limiting Middleware

// middleware/rate-limit.middleware.ts
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';

const redis = new Redis({
	host: process.env.REDIS_HOST,
	port: parseInt(process.env.REDIS_PORT || '6379')
});

export const apiLimiter = rateLimit({
	store: new RedisStore({
		client: redis,
		prefix: 'rl:'
	}),
	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',
	standardHeaders: true,
	legacyHeaders: false
});

export const authLimiter = rateLimit({
	store: new RedisStore({
		client: redis,
		prefix: 'rl:auth:'
	}),
	windowMs: 15 * 60 * 1000,
	max: 5, // Stricter limit for auth endpoints
	skipSuccessfulRequests: true
});

Request Logging Middleware

// middleware/logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
import pino from 'pino';

const logger = pino({
	level: process.env.LOG_LEVEL || 'info',
	transport: {
		target: 'pino-pretty',
		options: { colorize: true }
	}
});

export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
	const start = Date.now();

	// Log response when finished
	res.on('finish', () => {
		const duration = Date.now() - start;
		logger.info({
			method: req.method,
			url: req.url,
			status: res.statusCode,
			duration: `${duration}ms`,
			userAgent: req.headers['user-agent'],
			ip: req.ip
		});
	});

	next();
};

export { logger };

Error Handling

Custom Error Classes

// utils/errors.ts
export class AppError extends Error {
	constructor(
		public message: string,
		public statusCode: number = 500,
		public isOperational: boolean = true
	) {
		super(message);
		Object.setPrototypeOf(this, AppError.prototype);
		Error.captureStackTrace(this, this.constructor);
	}
}

export class ValidationError extends AppError {
	constructor(
		message: string,
		public errors?: any[]
	) {
		super(message, 400);
	}
}

export class NotFoundError extends AppError {
	constructor(message: string = 'Resource not found') {
		super(message, 404);
	}
}

export class UnauthorizedError extends AppError {
	constructor(message: string = 'Unauthorized') {
		super(message, 401);
	}
}

export class ForbiddenError extends AppError {
	constructor(message: string = 'Forbidden') {
		super(message, 403);
	}
}

export class ConflictError extends AppError {
	constructor(message: string) {
		super(message, 409);
	}
}

Global Error Handler

// middleware/error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors';
import { logger } from './logger.middleware';

export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
	if (err instanceof AppError) {
		return res.status(err.statusCode).json({
			status: 'error',
			message: err.message,
			...(err instanceof ValidationError && { errors: err.errors })
		});
	}

	// Log unexpected errors
	logger.error({
		error: err.message,
		stack: err.stack,
		url: req.url,
		method: req.method
	});

	// Don't leak error details in production
	const message = process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message;

	res.status(500).json({
		status: 'error',
		message
	});
};

// Async error wrapper
export const asyncHandler = (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) => {
	return (req: Request, res: Response, next: NextFunction) => {
		Promise.resolve(fn(req, res, next)).catch(next);
	};
};

Database Patterns

Node.js supports both SQL and NoSQL databases. Use connection pooling for all production databases.

Key patterns covered in references/advanced-patterns.md:

  • PostgreSQL with connection pool — pg Pool configuration and graceful shutdown
  • MongoDB with Mongoose — connection management and schema definition
  • Transaction pattern — BEGIN/COMMIT/ROLLBACK with pg client

Authentication & Authorization

JWT-based auth with access tokens (short-lived, 15m) and refresh tokens (7d). Full AuthService implementation with bcrypt password comparison in references/advanced-patterns.md.

Caching Strategies

Redis-backed CacheService with get/set/delete/invalidatePattern, plus a @Cacheable decorator for method-level caching. See references/advanced-patterns.md.

API Response Format

Standardized ApiResponse helper with success, error, and paginated static methods. See references/advanced-patterns.md.

Best Practices

  1. Use TypeScript: Type safety prevents runtime errors
  2. Implement proper error handling: Use custom error classes
  3. Validate input: Use libraries like Zod or Joi
  4. Use environment variables: Never hardcode secrets
  5. Implement logging: Use structured logging (Pino, Winston)
  6. Add rate limiting: Prevent abuse
  7. Use HTTPS: Always in production
  8. Implement CORS properly: Don't use * in production
  9. Use dependency injection: Easier testing and maintenance
  10. Write tests: Unit, integration, and E2E tests
  11. Handle graceful shutdown: Clean up resources
  12. Use connection pooling: For databases
  13. Implement health checks: For monitoring
  14. Use compression: Reduce response size
  15. Monitor performance: Use APM tools

Testing Patterns

See javascript-testing-patterns skill for comprehensive testing guidance.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works1822026년 10월 24일 업데이트

ai-sdk

무료

Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: "AI SDK", "Vercel AI SDK", "generateText", "streamText", "add AI to my app", "build an agent", "tool calling", "structured output", "useChat".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works1822026년 10월 24일 업데이트

Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables. Use when users mention Better Auth, betterauth, auth.ts, or need to set up TypeScript authentication with email/password, OAuth, or plugin configuration.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works1822026년 10월 24일 업데이트

Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works1822026년 10월 24일 업데이트

Configure email verification, implement password reset flows, set password policies, and customise hashing algorithms for Better Auth email/password authentication. Use when users need to set up login, sign-in, sign-up, credential authentication, or password security with Better Auth.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works1822026년 10월 24일 업데이트

Repo-specific guide to the Ever Works monorepo (ever-works/ever-works) — its pnpm + Turborepo layout, the per-workspace test runners (Jest vs Vitest vs Playwright), file-naming and import-alias conventions, the commit/branch model, and the real build/lint/type-check commands. Use this whenever working anywhere in this repository so changes match the codebase's actual conventions and pass CI.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ever-works/ever-works1822026년 10월 24일 업데이트

ever-works의 스킬 모두 보기

이 스킬의 문제 신고