Créer une API REST avec Node.js en 2026 : Guide Complet

Par Lucas M Dev — 28 mars 2026 — 15 min de lecture

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 :

  1. Pushez votre code sur GitHub
  2. Connectez Railway à votre repo
  3. Railway détecte Node.js automatiquement
  4. Ajoutez les variables d'environnement (JWT_SECRET, DATABASE_URL)
  5. Déploiement automatique à chaque push

Commande build à configurer dans Railway :

npm run build

Commande start :

node dist/index.js

→ Déployer gratuitement avec Railway, Render, Fly.io

→ TypeScript pour débutants en 2026