# js-backend-express-api-design > Design scalable Express.js REST APIs. Focuses on controller/service/repository layering, consistent response formatting, error handling, and API versioning. Use when creating new endpoints, refactoring monoliths, or establishing backend standards. - Author: Duy Pham - Repository: duypham2407/skills-workflows - Version: 20260125144132 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/duypham2407/skills-workflows - Web: https://mule.run/skillshub/@@duypham2407/skills-workflows~js-backend-express-api-design:20260125144132 --- --- name: js-backend-express-api-design description: Design scalable Express.js REST APIs. Focuses on controller/service/repository layering, consistent response formatting, error handling, and API versioning. Use when creating new endpoints, refactoring monoliths, or establishing backend standards. --- # Express.js API Design Best practices for structuring scalable Express.js applications. ## Layered Architecture Strict separation of concerns ensures maintainability and testability. ### 1. Controller Layer (`controllers/`) Handles HTTP requests, validation, and responses. **NO business logic.** ```javascript // controllers/user.controller.js const userService = require("../services/user.service"); const getUser = async (req, res, next) => { try { const { id } = req.params; const user = await userService.findById(id); res.json({ data: user }); } catch (error) { next(error); // Pass to error middleware } }; module.exports = { getUser }; ``` ### 2. Service Layer (`services/`) Contains **business logic**. Agnostic of HTTP (no `req`/`res`). ```javascript // services/user.service.js const userRepository = require("../repositories/user.repository"); const { NotFoundError } = require("../utils/errors"); const findById = async (id) => { const user = await userRepository.findUser(id); if (!user) { throw new NotFoundError(`User with ID ${id} not found`); } return user; }; module.exports = { findById }; ``` ### 3. Repository Layer (`repositories/`) Handles **data access**. Agnostic of business logic. ```javascript // repositories/user.repository.js const db = require("../db"); const findUser = async (id) => { return await db("users").where({ id }).first(); }; module.exports = { findUser }; ``` ## Response Formatting Use a consistent response envelope for all API endpoints. ```javascript // Success Response { "status": "success", "data": { ... }, "metadata": { // Optional: pagination, etc. "page": 1, "limit": 10, "total": 100 } } // Error Response { "status": "error", "message": "User not found", "code": "RESOURCE_NOT_FOUND", "errors": [ ... ] // Optional: validation details } ``` ### Implementation Helper ```javascript // utils/response.js const success = (res, data, code = 200) => { res.status(code).json({ status: "success", data, }); }; const error = (res, message, code = 500) => { res.status(code).json({ status: "error", message, }); }; ``` ## Error Handling Centralized error handling middleware is mandatory. ```javascript // middleware/error.middleware.js const errorHandler = (err, req, res, next) => { console.error(err); // Log the error const statusCode = err.statusCode || 500; const message = err.message || "Internal Server Error"; res.status(statusCode).json({ status: "error", message, ...(process.env.NODE_ENV === "development" && { stack: err.stack }), }); }; module.exports = errorHandler; ``` ### Custom Error Classes ```javascript // utils/errors.js class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.isOperational = true; } } class NotFoundError extends AppError { constructor(message = "Resource not found") { super(message, 404); } } ``` ## API Versioning Always version your APIs. Prefer URL versioning for simplicity. ```javascript // app.js const v1Router = require("./routes/v1"); const v2Router = require("./routes/v2"); app.use("/api/v1", v1Router); app.use("/api/v2", v2Router); ``` ### Directory Structure ``` src/ ├── app.js ├── routes/ │ └── v1/ │ └── users.route.js ├── controllers/ ├── services/ └── repositories/ ``` ## Validation (Joi/Zod) Validate input in the controller or middleware layer. ```javascript // middleware/validate.middleware.js const validate = (schema) => (req, res, next) => { const { error } = schema.validate(req.body); if (error) { return res.status(400).json({ status: "error", message: "Validation failed", errors: error.details.map((d) => d.message), }); } next(); }; ``` ## References - [rest-standards.md](references/rest-standards.md) - HTTP methods, status codes, and URI conventions - [data-layer.md](references/data-layer.md) - Repository pattern and database abstraction