Créer une API REST avec Node.js en 2026 : Guide Complet
Node.js + Express reste en 2026 l'une des stacks les plus utilisées pour créer des APIs backend. Ce guide vous montre comment créer une API REST complète de zéro : structure du projet, CRUD, validation, authentification JWT, et déploiement.
Sommaire
Setup du projet
# Créer le projet
mkdir mon-api && cd mon-api
npm init -y
# Installer les dépendances
npm install express cors helmet dotenv
npm install --save-dev typescript ts-node-dev @types/express @types/cors @types/node
# Initialiser TypeScript
npx tsc --init
Configuration TypeScript minimale
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
}
}
Structure du projet recommandée
mon-api/
├── src/
│ ├── controllers/ # Logique métier
│ │ └── users.controller.ts
│ ├── routes/ # Définition des routes
│ │ └── users.routes.ts
│ ├── middleware/ # Middlewares (auth, validation)
│ │ └── auth.middleware.ts
│ ├── types/ # Types TypeScript
│ │ └── index.ts
│ └── index.ts # Point d'entrée
├── .env
├── package.json
└── tsconfig.json
Point d'entrée — src/index.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import dotenv from 'dotenv';
import usersRouter from './routes/users.routes';
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
// Middlewares
app.use(helmet()); // Headers de sécurité
app.use(cors()); // Cross-Origin Resource Sharing
app.use(express.json()); // Parser le JSON
// Routes
app.use('/api/users', usersRouter);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Gestion des erreurs globale
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err.stack);
res.status(500).json({ error: 'Erreur interne du serveur' });
});
app.listen(PORT, () => {
console.log(`🚀 API démarrée sur http://localhost:${PORT}`);
});
Routes CRUD
// src/routes/users.routes.ts
import { Router } from 'express';
import {
getAllUsers, getUserById, createUser, updateUser, deleteUser
} from '../controllers/users.controller';
import { authMiddleware } from '../middleware/auth.middleware';
const router = Router();
router.get('/', getAllUsers);
router.get('/:id', getUserById);
router.post('/', createUser);
router.put('/:id', authMiddleware, updateUser); // Protégé
router.delete('/:id', authMiddleware, deleteUser); // Protégé
export default router;
// src/controllers/users.controller.ts
import { Request, Response } from 'express';
// Simulation d'une base de données en mémoire
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
let nextId = 3;
export const getAllUsers = (req: Request, res: Response) => {
const { page = '1', limit = '10' } = req.query;
const p = parseInt(page as string);
const l = parseInt(limit as string);
const start = (p - 1) * l;
const paginated = users.slice(start, start + l);
res.json({
data: paginated,
total: users.length,
page: p,
totalPages: Math.ceil(users.length / l)
});
};
export const getUserById = (req: Request, res: Response) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'Utilisateur non trouvé' });
res.json(user);
};
export const createUser = (req: Request, res: Response) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'name et email sont requis' });
}
if (users.some(u => u.email === email)) {
return res.status(409).json({ error: 'Email déjà utilisé' });
}
const newUser = { id: nextId++, name, email };
users.push(newUser);
res.status(201).json(newUser);
};
export const updateUser = (req: Request, res: Response) => {
const idx = users.findIndex(u => u.id === parseInt(req.params.id));
if (idx === -1) return res.status(404).json({ error: 'Utilisateur non trouvé' });
users[idx] = { ...users[idx], ...req.body, id: users[idx].id };
res.json(users[idx]);
};
export const deleteUser = (req: Request, res: Response) => {
const idx = users.findIndex(u => u.id === parseInt(req.params.id));
if (idx === -1) return res.status(404).json({ error: 'Utilisateur non trouvé' });
users.splice(idx, 1);
res.status(204).send();
};
Validation avec Zod
Zod est la bibliothèque de validation recommandée en 2026. Elle s'intègre parfaitement avec TypeScript.
npm install zod
import { z } from 'zod';
// Schéma de validation
const createUserSchema = z.object({
name: z.string().min(2, 'Le nom doit faire au moins 2 caractères').max(100),
email: z.string().email('Email invalide'),
age: z.number().min(18).max(120).optional()
});
// Middleware de validation
export const validateBody = (schema: z.ZodSchema) => {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Données invalides',
details: result.error.flatten().fieldErrors
});
}
req.body = result.data;
next();
};
};
// Dans la route
router.post('/', validateBody(createUserSchema), createUser);
Authentification avec JWT
npm install jsonwebtoken bcryptjs
npm install --save-dev @types/jsonwebtoken @types/bcryptjs
// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface JWTPayload {
userId: number;
email: string;
}
declare global {
namespace Express {
interface Request {
user?: JWTPayload;
}
}
}
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Token manquant' });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ error: 'Token invalide ou expiré' });
}
};
// Route de login
export const loginHandler = async (req: Request, res: Response) => {
const { email, password } = req.body;
// Vérifier l'utilisateur en base de données...
const user = await findUserByEmail(email);
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
res.json({ token, user: { id: user.id, email: user.email } });
};
Base de données avec Prisma
Prisma est l'ORM recommandé en 2026 pour Node.js. Type-safe, performant, et excellent DX.
npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}
# Créer et appliquer la migration
npx prisma migrate dev --name init
# Générer le client Prisma
npx prisma generate
Déploiement sur Railway
# Dockerfile minimal (optionnel mais recommandé)
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
EXPOSE 3000
CMD ["node", "dist/index.js"]
Avec Railway, le déploiement est simple :
- Pushez votre code sur GitHub
- Connectez Railway à votre repo
- Railway détecte Node.js automatiquement
- Ajoutez les variables d'environnement (JWT_SECRET, DATABASE_URL)
- Déploiement automatique à chaque push
Commande build à configurer dans Railway :
npm run build
Commande start :
node dist/index.js