Backend Typescript 1.0.0 Help

ExpressJS

Что такое Express и зачем он нужен

Express — это минималистичный фреймворк для Node.js, который упрощает создание HTTP-серверов и веб-приложений. Он предоставляет удобные абстракции над низкоуровневым модулем http, позволяя быстрее определять маршруты, подключать middleware, обрабатывать файлы, параметры, заголовки и ошибки. На Express строят REST API, серверный рендеринг страниц и backend для SPA/мобильных приложений.

  • Ускоряет разработку благодаря готовым примитивам: app, Router, req/res, next.

  • Гибко расширяется через экосистему пакетов: логирование, CORS, куки, сессии, валидация, загрузка файлов.

  • Не навязывает архитектуру — вы сами выбираете слои и структуру каталогов.

Как работают серверные приложения и основные концепции

Серверное приложение «слушает» порт и принимает входящие запросы. Каждый запрос проходит через цепочку middleware — маленьких функций, которые могут читать/изменять req (запрос), res (ответ), либо завершать обработку, либо передавать управление дальше через next(). Когда найден подходящий маршрут, вызывается его обработчик, формирующий ответ.

  • Приложение (app) — центральный объект, к которому «подвешивают» middleware и маршруты.

  • Middleware — функции пред- и пост-обработки: парсинг тела, аутентификация, логирование, ограничения скорости.

  • Маршрутизатор (Router) — модуль для группировки маршрутов по домену (например, /users).

  • Обработчик ошибок — специальное middleware с сигнатурой (err, req, res, next).

Правильная структура файлов

Express не навязывает структуру, но практично разделять слои и домены. Ниже — опорный шаблон для REST API со слоями контроллеров, сервисов, репозиториев и утилит.

project/ src/ app.ts server.ts config/ index.ts modules/ users/ users.router.ts users.controller.ts users.service.ts users.repository.ts users.validators.ts users.types.ts auth/ auth.router.ts auth.controller.ts auth.service.ts middlewares/ error.middleware.ts not-found.middleware.ts request-logger.middleware.ts libs/ db.ts http.ts utils/ env.ts tests/ users.e2e.test.ts public/ index.html .env package.json tsconfig.json
  • app.ts — сборка приложения: подключение middleware/маршрутов.

  • server.ts — запуск HTTP-сервера, чтение порта из окружения.

  • modules/* — доменная логика: роуты, контроллеры, сервисы, репозитории, валидация.

  • middlewares/* — кросс-срезовые задачи: логирование, безопасность, обработка ошибок.

  • config/* и utils/* — конфигурация и вспомогательные функции.

  • public/* — статические файлы, если нужно раздавать их напрямую.

Минимальное приложение: запуск и первый маршрут

npm init -y npm i express node src/server.js
/* src/server.js */ const express = require("express"); const app = express(); app.use(express.json()); app.get("/", (req, res) => { res.status(200).send("Hello, Express!"); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server started on http://localhost:${PORT}`); // выведем адрес для проверки });

Базовые сущности и инструменты Express

Приложение и Router

app — корневой объект. Router() позволяет группировать маршруты по доменам и подключать локальные middleware.

// src/modules/users/users.router.ts import { Router } from "express"; import * as controller from "./users.controller"; export const usersRouter = Router(); usersRouter.get("/", controller.list); usersRouter.post("/", controller.create); usersRouter.get("/:id", controller.getById); usersRouter.patch("/:id", controller.update); usersRouter.delete("/:id", controller.remove);
// src/app.ts import express from "express"; import { usersRouter } from "./modules/users/users.router"; export const app = express(); app.use(express.json()); app.use("/users", usersRouter);
// src/server.ts import { app } from "./app"; const PORT = Number(process.env.PORT) || 3000; app.listen(PORT, () => { console.log(`Server on http://localhost:${PORT}`); // проверка, что сервер поднят });

Контроллеры, сервисы, репозитории

Контроллер преобразует HTTP-запрос в вызовы доменной логики, сервис реализует бизнес-правила, репозиторий общается с БД/внешними системами.

// src/modules/users/users.controller.ts import { Request, Response, NextFunction } from "express"; import * as service from "./users.service"; export async function list(req: Request, res: Response, next: NextFunction) { try { const users = await service.list(); res.status(200).json(users); } catch (err) { next(err); } } export async function create(req: Request, res: Response, next: NextFunction) { try { const user = await service.create(req.body); res.status(201).json(user); } catch (err) { next(err); } } export async function getById(req: Request, res: Response, next: NextFunction) { try { const user = await service.getById(req.params.id); if (!user) return res.status(404).json({ error: "Not found" }); res.json(user); } catch (err) { next(err); } } export async function update(req: Request, res: Response, next: NextFunction) { try { const user = await service.update(req.params.id, req.body); res.json(user); } catch (err) { next(err); } } export async function remove(req: Request, res: Response, next: NextFunction) { try { await service.remove(req.params.id); res.status(204).send(); } catch (err) { next(err); } }
// src/modules/users/users.service.ts import * as repo from "./users.repository"; import {User} from "./users.types"; export async function list(): Promise<User[]> { return repo.findAll(); } export async function create(data: Partial<User>): Promise<User> { return repo.insert(data); } export async function getById(id: string): Promise<User | undefined> { return repo.findById(id); } export async function update(id: string, data: Partial<User>): Promise<User> { return repo.update(id, data); } export async function remove(id: string): Promise<void> { return repo.remove(id); }
// src/modules/users/users.repository.ts import {randomUUID} from "crypto"; import {User} from "./users.types"; const memory: User[] = []; export async function findAll(): Promise<User[]> { return memory; } export async function insert(data: Partial<User>): Promise<User> { const item: User = {id: randomUUID(), name: data.name ?? "", email: data.email ?? ""}; memory.push(item); return item; } export async function findById(id: string): Promise<User | undefined> { return memory.find(u => u.id === id); } export async function update(id: string, data: Partial<User>): Promise<User> { const idx = memory.findIndex(u => u.id === id); if (idx === -1) throw new Error("Not found"); memory[idx] = {...memory[idx], ...data}; return memory[idx]; } export async function remove(id: string): Promise<void> { const idx = memory.findIndex(u => u.id === id); if (idx !== -1) memory.splice(idx, 1); }

Middleware: общая пред-/пост-обработка

Middleware принимает (req, res, next). Оно может завершить ответ или передать управление дальше вызовом next(). Порядок подключения влияет на поведение.

// src/middlewares/request-logger.middleware.ts import { Request, Response, NextFunction } from "express"; export function requestLogger(req: Request, res: Response, next: NextFunction) { const started = Date.now(); res.on("finish", () => { const ms = Date.now() - started; console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${ms}ms`); // лог запроса }); next(); }
// src/app.ts (фрагмент подключения логгера) import { requestLogger } from "./middlewares/request-logger.middleware"; // ... после создания app app.use(requestLogger);
// src/middlewares/error.middleware.ts import { Request, Response, NextFunction } from "express"; export function errorMiddleware(err: unknown, req: Request, res: Response, _next: NextFunction) { const status = 500; const message = err instanceof Error ? err.message : "Internal Server Error"; res.status(status).json({ error: message }); }
// src/app.ts (подключение обработчика ошибок в самом конце) import { errorMiddleware } from "./middlewares/error.middleware"; // ... после всех маршрутов и middleware app.use(errorMiddleware);

Парсинг тела, статические файлы, CORS

Частые утилиты: express.json() для JSON-тел, express.urlencoded() для форм, express.static() для статики, пакет cors для CORS.

// src/app.ts (фрагмент: парсинг, статика, CORS) import cors from "cors"; import path from "path"; import express from "express"; app.use(cors()); app.use(express.json({ limit: "1mb" })); app.use(express.urlencoded({ extended: true })); app.use("/static", express.static(path.join(process.cwd(), "public")));

Валидация входных данных

Проверяйте параметры, req.body и req.query до бизнес-логики. Можно использовать express-validator или zod/yup.

// src/modules/users/users.validators.ts import { body, param } from "express-validator"; export const createUserDto = [ body("email").isEmail(), body("name").isString().isLength({ min: 2 }) ]; export const idParam = [ param("id").isUUID() ];
// src/middlewares/validate.middleware.ts import { validationResult } from "express-validator"; import { Request, Response, NextFunction } from "express"; export function validate(req: Request, res: Response, next: NextFunction) { const errors = validationResult(req); if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() }); next(); }

Асинхронные обработчики и ловля ошибок

Асинхронные контроллеры должны передавать ошибки в общий обработчик. Оберните их в небольшую утилиту.

import { Request, Response, NextFunction } from "express"; export const asyncHandler = (fn: (req: Request, res: Response, next: NextFunction) => unknown) => (req: Request, res: Response, next: NextFunction) => Promise.resolve(fn(req, res, next)).catch(next);
// пример использования asyncHandler в роутере import { Router } from "express"; import { asyncHandler } from "../../libs/http"; const router = Router(); router.get("/:id", asyncHandler(async (req, res) => { res.json({ id: req.params.id }); })); export default router;

Конфигурация и переменные окружения

Чувствительные значения храните в окружении (.env) и валидируйте при старте.

// src/utils/env.ts import * as dotenv from "dotenv"; dotenv.config(); export const env = { NODE_ENV: process.env.NODE_ENV || "development", PORT: Number(process.env.PORT || 3000) };

Практические советы и подводные камни

  • Порядок middleware важен: логирование и CORS — раньше маршрутизации; обработчик ошибок — в самом конце.

  • Всегда возвращайте корректные HTTP-статусы и явные JSON-структуры ошибок.

  • Ограничивайте размер тела запроса и время обработки, добавляйте таймауты и rate limiting.

  • Не смешивайте домены: держите отдельные Router для разных модулей.

  • Добавьте наблюдаемость: логирование запросов, корреляционные ID, метрики времени ответа.

Last modified: 01 October 2025