--- title: LLMs Best Practices - Node.js url: https://colingoudie.me/llms-node.txt description: General Node.js conventions for AI-assisted development related: - https://colingoudie.me/llms.txt --- # LLMs Best Practices - Node.js Include this in your CLAUDE.md or similar AI assistant configuration file. See also: https://colingoudie.me/llms.txt ## Project Structure ``` src/ config/ # Configuration and environment controllers/ # Route handlers (thin) services/ # Business logic repositories/ # Data access layer models/ # Data models/types middleware/ # Express/Fastify middleware utils/ # Shared utilities routes/ # Route definitions index.ts # Entry point tests/ unit/ integration/ fixtures/ ``` ## TypeScript Setup ### tsconfig.json Essentials ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist", "rootDir": "./src", "declaration": true, "declarationMap": true, "sourceMap": true, "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` ### Package.json Scripts ```json { "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js", "test": "vitest", "test:coverage": "vitest --coverage", "lint": "eslint src --ext .ts", "typecheck": "tsc --noEmit" } } ``` ## Express Patterns ### Controller Pattern ```typescript // controllers/projects.controller.ts import { Request, Response, NextFunction } from "express"; import { ProjectService } from "@/services/project.service"; export class ProjectController { constructor(private projectService: ProjectService) {} getAll = async (req: Request, res: Response, next: NextFunction) => { try { const projects = await this.projectService.findAll(req.query); res.json({ data: projects }); } catch (error) { next(error); } }; create = async (req: Request, res: Response, next: NextFunction) => { try { const project = await this.projectService.create(req.body); res.status(201).json({ data: project }); } catch (error) { next(error); } }; } ``` ### Error Handling Middleware ```typescript // middleware/error.middleware.ts import { Request, Response, NextFunction } from "express"; export class AppError extends Error { constructor( public statusCode: number, message: string, public isOperational = true ) { super(message); } } export function errorHandler( err: Error, req: Request, res: Response, next: NextFunction ) { if (err instanceof AppError) { return res.status(err.statusCode).json({ error: { message: err.message, ...(process.env.NODE_ENV === "development" && { stack: err.stack }), }, }); } console.error("Unexpected error:", err); res.status(500).json({ error: { message: "Internal server error" }, }); } ``` ### Request Validation ```typescript // middleware/validate.middleware.ts import { z } from "zod"; import { Request, Response, NextFunction } from "express"; export function validate(schema: z.ZodSchema) { return (req: Request, res: Response, next: NextFunction) => { try { schema.parse({ body: req.body, query: req.query, params: req.params, }); next(); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: { message: "Validation failed", details: error.errors, }, }); } next(error); } }; } // Usage const createProjectSchema = z.object({ body: z.object({ name: z.string().min(1).max(100), description: z.string().optional(), }), }); router.post("/projects", validate(createProjectSchema), controller.create); ``` ## Fastify Patterns ### Plugin Structure ```typescript // plugins/projects.plugin.ts import { FastifyPluginAsync } from "fastify"; const projectsPlugin: FastifyPluginAsync = async (fastify) => { fastify.get("/projects", { schema: { response: { 200: { type: "object", properties: { data: { type: "array" }, }, }, }, }, handler: async (request, reply) => { const projects = await fastify.projectService.findAll(); return { data: projects }; }, }); }; export default projectsPlugin; ``` ## Service Layer ```typescript // services/project.service.ts import { ProjectRepository } from "@/repositories/project.repository"; import { CreateProjectInput, Project, ProjectFilters } from "@/models/project"; import { AppError } from "@/middleware/error.middleware"; export class ProjectService { constructor(private repository: ProjectRepository) {} async findAll(filters: ProjectFilters): Promise { return this.repository.findAll(filters); } async findById(id: string): Promise { const project = await this.repository.findById(id); if (!project) { throw new AppError(404, "Project not found"); } return project; } async create(input: CreateProjectInput): Promise { return this.repository.create(input); } } ``` ## Repository Pattern ```typescript // repositories/project.repository.ts import { db } from "@/config/database"; import { Project, CreateProjectInput, ProjectFilters } from "@/models/project"; export class ProjectRepository { async findAll(filters: ProjectFilters): Promise { const query = db.selectFrom("projects").selectAll(); if (filters.status) { query.where("status", "=", filters.status); } return query.execute(); } async findById(id: string): Promise { return db .selectFrom("projects") .selectAll() .where("id", "=", id) .executeTakeFirst(); } async create(input: CreateProjectInput): Promise { return db .insertInto("projects") .values(input) .returningAll() .executeTakeFirstOrThrow(); } } ``` ## Environment Configuration ```typescript // config/env.ts import { z } from "zod"; const envSchema = z.object({ NODE_ENV: z.enum(["development", "test", "production"]).default("development"), PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), }); export const env = envSchema.parse(process.env); ``` ## Testing Patterns ### Unit Tests ```typescript // tests/unit/services/project.service.test.ts import { describe, it, expect, vi, beforeEach } from "vitest"; import { ProjectService } from "@/services/project.service"; import { ProjectRepository } from "@/repositories/project.repository"; describe("ProjectService", () => { let service: ProjectService; let mockRepository: Partial; beforeEach(() => { mockRepository = { findById: vi.fn(), create: vi.fn(), }; service = new ProjectService(mockRepository as ProjectRepository); }); describe("findById", () => { it("should return project when found", async () => { const project = { id: "1", name: "Test" }; mockRepository.findById = vi.fn().mockResolvedValue(project); const result = await service.findById("1"); expect(result).toEqual(project); }); it("should throw AppError when not found", async () => { mockRepository.findById = vi.fn().mockResolvedValue(null); await expect(service.findById("1")).rejects.toThrow("Project not found"); }); }); }); ``` ### Integration Tests ```typescript // tests/integration/projects.test.ts import { describe, it, expect, beforeAll, afterAll } from "vitest"; import request from "supertest"; import { app } from "@/app"; import { db } from "@/config/database"; describe("Projects API", () => { beforeAll(async () => { await db.migrate.latest(); }); afterAll(async () => { await db.destroy(); }); describe("GET /api/projects", () => { it("should return empty array when no projects", async () => { const response = await request(app).get("/api/projects"); expect(response.status).toBe(200); expect(response.body.data).toEqual([]); }); }); }); ``` ## Logging ```typescript // config/logger.ts import pino from "pino"; export const logger = pino({ level: process.env.LOG_LEVEL || "info", transport: process.env.NODE_ENV === "development" ? { target: "pino-pretty" } : undefined, }); // Usage logger.info({ userId: user.id }, "User logged in"); logger.error({ err, requestId }, "Request failed"); ```