0 XP
?
Intermédiaire120 min80 XP

Objectifs de cette leçon

  • Construire une API REST complète
  • Implémenter CRUD avec Express
  • Structurer un projet Node.js professionnel

Projet Final Node.js : API REST complète 🏆

Objectif

Construire une API REST complète pour un blog avec :

  • Authentification JWT
  • CRUD des articles
  • Système de commentaires
  • Prisma + SQLite

Architecture du projet

blog-api/
├── prisma/
│   └── schema.prisma
├── src/
│   ├── server.js
│   ├── routes/
│   │   ├── auth.js
│   │   ├── posts.js
│   │   └── comments.js
│   ├── middleware/
│   │   ├── auth.js
│   │   └── validate.js
│   └── db.js
├── .env
└── package.json

Schéma de la base de données

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  password  String
  posts     Post[]
  comments  Comment[]
  createdAt DateTime @default(now())
}

model Post {
  id        String    @id @default(cuid())
  title     String
  content   String
  published Boolean   @default(false)
  author    User      @relation(fields: [authorId], references: [id])
  authorId  String
  comments  Comment[]
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
}

model Comment {
  id        String   @id @default(cuid())
  text      String
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  post      Post     @relation(fields: [postId], references: [id])
  postId    String
  createdAt DateTime @default(now())
}

Serveur principal

import express from 'express';
import authRouter from './routes/auth.js';
import postsRouter from './routes/posts.js';
import commentsRouter from './routes/comments.js';

const app = express();
app.use(express.json());

app.use('/api/auth', authRouter);
app.use('/api/posts', postsRouter);
app.use('/api/comments', commentsRouter);

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({ error: err.message });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Blog API sur http://localhost:${PORT}`);
});

Routes d'authentification

import { Router } from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { prisma } from '../db.js';

const router = Router();
const SECRET = process.env.JWT_SECRET;

router.post('/register', async (req, res, next) => {
  try {
    const { email, name, password } = req.body;
    const hash = await bcrypt.hash(password, 10);
    const user = await prisma.user.create({
      data: { email, name, password: hash }
    });
    res.status(201).json({ id: user.id, email: user.email, name: user.name });
  } catch (err) {
    if (err.code === 'P2002') {
      return res.status(409).json({ error: 'Email déjà utilisé' });
    }
    next(err);
  }
});

router.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await prisma.user.findUnique({ where: { email } });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: 'Identifiants incorrects' });
  }
  const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '24h' });
  res.json({ token });
});

export default router;

Routes des articles

import { Router } from 'express';
import { prisma } from '../db.js';
import { authMiddleware } from '../middleware/auth.js';

const router = Router();

router.get('/', async (req, res) => {
  const posts = await prisma.post.findMany({
    where: { published: true },
    include: { author: { select: { name: true } } },
    orderBy: { createdAt: 'desc' }
  });
  res.json(posts);
});

router.get('/:id', async (req, res) => {
  const post = await prisma.post.findUnique({
    where: { id: req.params.id },
    include: { author: true, comments: { include: { author: true } } }
  });
  if (!post) return res.status(404).json({ error: 'Non trouvé' });
  res.json(post);
});

router.post('/', authMiddleware, async (req, res) => {
  const { title, content, published } = req.body;
  const post = await prisma.post.create({
    data: { title, content, published, authorId: req.user.userId }
  });
  res.status(201).json(post);
});

router.put('/:id', authMiddleware, async (req, res) => {
  const post = await prisma.post.findUnique({ where: { id: req.params.id } });
  if (!post || post.authorId !== req.user.userId) {
    return res.status(403).json({ error: 'Non autorisé' });
  }
  const updated = await prisma.post.update({
    where: { id: req.params.id },
    data: req.body
  });
  res.json(updated);
});

router.delete('/:id', authMiddleware, async (req, res) => {
  const post = await prisma.post.findUnique({ where: { id: req.params.id } });
  if (!post || post.authorId !== req.user.userId) {
    return res.status(403).json({ error: 'Non autorisé' });
  }
  await prisma.post.delete({ where: { id: req.params.id } });
  res.status(204).send();
});

export default router;

Extensions possibles

  • Upload d'images
  • Likes / favoris
  • Rôles admin/modérateur
  • Rate limiting
  • Tests automatisés (Jest)
  • Déploiement (Railway, Render)

🎉 Bravo ! Tu as construit une API REST professionnelle.

⚡ Simulateur — Projet final

Étape 1/12
📝 Pseudo-code
1const express = require("express");
2const app = express();
3let users = [{ id: 1, name: "Alice" }];
4
5app.get("/users", (req, res) => res.json(users));
6
7app.post("/users", express.json(), (req, res) => {
8 const newUser = { id: users.length + 1, ...req.body };
9 users.push(newUser);
10 res.status(201).json(newUser);
11});
12
13app.delete("/users/:id", (req, res) => {
14 users = users.filter((u) => u.id !== +req.params.id);
15 res.json({ deleted: true });
16});
17
18app.listen(3000);
⚙️ Exécution
📦 Mémoire
Aucune variable
🔀 Flux d'exécution
require("express")
3
Import d'Express

require("express") importe le framework Express pour créer rapidement une API REST.