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.