diff --git a/correction/prisma/schema.prisma b/correction/prisma/schema.prisma
new file mode 100644
index 0000000000000000000000000000000000000000..a6a46a78f1a0265af9fb2447d439015556ee9ad8
--- /dev/null
+++ b/correction/prisma/schema.prisma
@@ -0,0 +1,33 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+generator client {
+  provider = "prisma-client-js"
+}
+
+datasource db {
+  provider = "sqlite"
+  url      = env("DATABASE_URL")
+}
+
+model Author {
+  id        Int    @id @default(autoincrement())
+  firstname String
+  lastname  String
+  books     Book[]
+}
+
+model Book {
+  id               Int    @id @default(autoincrement())
+  title            String
+  publication_year Int?
+  author           Author @relation(fields: [authorId], references: [id], onDelete: Cascade)
+  authorId         Int
+  tags             Tag[]
+}
+
+model Tag {
+  id    Int    @id @default(autoincrement())
+  name  String @unique
+  books Book[]
+}
diff --git a/correction/src/db.ts b/correction/src/db.ts
new file mode 100644
index 0000000000000000000000000000000000000000..39b43888511ca03a3b7115a6c32f300d30af0992
--- /dev/null
+++ b/correction/src/db.ts
@@ -0,0 +1,6 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+prisma.$connect();
+
+export { prisma };
diff --git a/correction/src/error.ts b/correction/src/error.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b3313810834536908bee2c105efb5964ade4c4c
--- /dev/null
+++ b/correction/src/error.ts
@@ -0,0 +1,8 @@
+export class HttpError extends Error {
+  status?: number; // optionnel, afin de rester compatible avec le type Error standard
+
+  constructor(message: string, status: number) {
+    super(message);
+    this.status = status;
+  }
+}
diff --git a/correction/src/index.ts b/correction/src/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a032f4602aa8823265c67731cc527ae9c206a7ce
--- /dev/null
+++ b/correction/src/index.ts
@@ -0,0 +1,46 @@
+import express, { Request, Response, NextFunction } from 'express';
+import { HttpError } from './error';
+import { StructError } from 'superstruct';
+
+import * as author from './requestHandlers/author';
+import * as book from './requestHandlers/book';
+import * as tag from './requestHandlers/tag';
+
+const app = express();
+const port = 3000;
+
+app.use(express.json());
+
+app.get('/authors', author.get_all);
+app.get('/authors/:author_id', author.get_one);
+app.post('/authors', author.create_one);
+app.patch('/authors/:author_id', author.update_one);
+app.delete('/authors/:author_id', author.delete_one);
+
+app.get('/books', book.get_all);
+app.get('/books/:book_id', book.get_one);
+app.get('/authors/:author_id/books', book.get_all_of_author);
+app.post('/authors/:author_id/books', book.create_one_of_author);
+app.patch('/books/:book_id', book.update_one);
+app.delete('/books/:book_id', book.delete_one);
+
+app.get('/tags', tag.get_all);
+app.get('/tags/:tag_id', tag.get_one);
+app.get('/books/:book_id/tags', tag.get_all_of_book);
+app.post('/tags', tag.create_one);
+app.patch('/tags/:tag_id', tag.update_one);
+app.delete('/tags/:tag_id', tag.delete_one);
+app.post('/books/:book_id/tags/:tag_id', tag.add_one_to_book);
+app.delete('/books/:book_id/tags/:tag_id', tag.remove_one_from_book);
+
+app.use((err: HttpError, req: Request, res: Response, next: NextFunction) => {
+  if (err instanceof StructError) {
+    err.status = 400;
+    err.message = `Bad value for field ${err.key}`;
+  }
+  return res.status(err.status ?? 500).send(err.message);
+});
+
+app.listen(port, () => {
+  console.log(`App listening on port ${port}`);
+});
diff --git a/correction/src/requestHandlers/author.ts b/correction/src/requestHandlers/author.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8ec348eb86c6905c72e25395d11d94baeac02cfc
--- /dev/null
+++ b/correction/src/requestHandlers/author.ts
@@ -0,0 +1,82 @@
+import { prisma } from '../db';
+import { Prisma } from '@prisma/client';
+import type { Request, Response } from 'express';
+import { HttpError } from '../error';
+import { assert } from 'superstruct';
+import { AuthorCreationData, AuthorUpdateData, AuthorGetAllQuery } from '../validation/author';
+
+export async function get_all(req: Request, res: Response) {
+  assert(req.query, AuthorGetAllQuery);
+  const { lastname, hasBooks, include, skip, take } = req.query;
+  const filter: Prisma.AuthorWhereInput = {};
+  if (lastname) {
+    filter.lastname = { contains: String(lastname) };
+  }
+  if (hasBooks === 'true') {
+    filter.books = { some: {} };
+  }
+  const assoc: Prisma.AuthorInclude = {};
+  if (include === 'books') {
+    assoc.books = { select: { id: true, title: true }, orderBy: { title: 'asc' } };
+  }
+  const authors = await prisma.author.findMany({
+    where: filter,
+    include: assoc,
+    orderBy: { lastname: 'asc' },
+    skip: skip ? Number(skip) : undefined,
+    take: take ? Number(take) : undefined
+  });
+  const authorCount = await prisma.author.count({ where: filter });
+  res.header('X-Total-Count', String(authorCount));
+  res.json(authors);
+};
+
+export async function get_one(req: Request, res: Response) {
+  const author = await prisma.author.findUnique({
+    where: {
+      id: Number(req.params.author_id)
+    }
+  });
+  if (!author) {
+    throw new HttpError('Author not found', 404);
+  }
+  res.json(author);
+};
+
+export async function create_one(req: Request, res: Response) {
+  assert(req.body, AuthorCreationData);
+  const author = await prisma.author.create({
+    data: req.body
+  });
+  res.status(201).json(author);
+};
+
+export async function update_one(req: Request, res: Response) {
+  assert(req.body, AuthorUpdateData);
+  try {
+    const author = await prisma.author.update({
+      where: {
+        id: Number(req.params.author_id)
+      },
+      data: req.body
+    });
+    res.json(author);
+  }
+  catch (err) {
+    throw new HttpError('Author not found', 404);
+  }
+};
+
+export async function delete_one(req: Request, res: Response) {
+  try {
+    await prisma.author.delete({
+      where: {
+        id: Number(req.params.author_id)
+      }
+    });
+    res.status(204).send();
+  }
+  catch (err) {
+    throw new HttpError('Author not found', 404);
+  }
+};
diff --git a/correction/src/requestHandlers/book.ts b/correction/src/requestHandlers/book.ts
new file mode 100644
index 0000000000000000000000000000000000000000..53db40cae68cef5e0fa9ce6ad5dc6f8945f16d90
--- /dev/null
+++ b/correction/src/requestHandlers/book.ts
@@ -0,0 +1,107 @@
+import { prisma } from '../db';
+import { Prisma } from '@prisma/client';
+import type { Request, Response } from 'express';
+import { HttpError } from '../error';
+import { assert } from 'superstruct';
+import { BookCreationData, BookUpdateData } from '../validation/book';
+
+export async function get_all(req: Request, res: Response) {
+  const { title, include, skip, take } = req.query;
+  const filter: Prisma.BookWhereInput = {};
+  if (title) {
+    filter.title = { contains: String(title) };
+  }
+  const assoc: Prisma.BookInclude = {};
+  if (include === 'author') {
+    assoc.author = { select: { id: true, firstname: true, lastname: true } };
+  }
+  const books = await prisma.book.findMany({
+    where: filter,
+    include: assoc,
+    orderBy: { title: 'asc' },
+    skip: skip ? Number(skip) : undefined,
+    take: take ? Number(take) : undefined
+  });
+  const bookCount = await prisma.book.count({ where: filter });
+  res.header('X-Total-Count', String(bookCount));
+  res.json(books);
+};
+
+export async function get_one(req: Request, res: Response) {
+  const book = await prisma.book.findUnique({
+    where: {
+      id: Number(req.params.book_id)
+    }
+  });
+  if (!book) {
+    throw new HttpError('Book not found', 404);
+  }
+  res.json(book);
+};
+
+export async function get_all_of_author(req: Request, res: Response) {
+  const { title } = req.query;
+  const filter: Prisma.BookWhereInput = {};
+  if (title) {
+    filter.title = { contains: String(title) };
+  }
+  const author = await prisma.author.findUnique({
+    where: {
+      id: Number(req.params.author_id),
+    },
+    include: {
+      books: {
+        where: filter,
+      }
+    }
+  });
+  if (!author) {
+    throw new HttpError('Author not found', 404);
+  }
+  res.json(author.books);
+};
+
+export async function create_one_of_author(req: Request, res: Response) {
+  assert(req.body, BookCreationData);
+  const book = await prisma.book.create({
+    data: {
+      ...req.body,
+      author: {
+        connect: {
+          id: Number(req.params.author_id)
+        }
+      }
+    }
+  });
+  res.status(201).json(book);
+};
+
+export async function update_one(req: Request, res: Response) {
+  assert(req.body, BookUpdateData);
+  try {
+    const book = await prisma.book.update({
+      where: {
+        id: Number(req.params.book_id)
+      },
+      data: req.body
+    });
+    res.json(book);
+  }
+  catch (err) {
+    throw new HttpError('Book not found', 404);
+  }
+};
+
+export async function delete_one(req: Request, res: Response) {
+  try {
+    await prisma.book.delete({
+      where: {
+        id: Number(req.params.book_id)
+      }
+    });
+    res.status(204).send();
+  }
+  catch (err) {
+    throw new HttpError('Book not found', 404);
+  }
+};
diff --git a/correction/src/requestHandlers/tag.ts b/correction/src/requestHandlers/tag.ts
new file mode 100644
index 0000000000000000000000000000000000000000..300abeac1dad3eba8312ae25ce726cb9bb12aed1
--- /dev/null
+++ b/correction/src/requestHandlers/tag.ts
@@ -0,0 +1,107 @@
+import { prisma } from '../db';
+import type { Request, Response } from 'express';
+import { HttpError } from '../error';
+import { assert } from 'superstruct';
+import { TagCreationData, TagUpdateData } from '../validation/tag';
+
+export async function get_all(req: Request, res: Response) {
+  const tags = await prisma.tag.findMany();
+  res.json(tags);
+};
+
+export async function get_one(req: Request, res: Response) {
+  const tag = await prisma.tag.findUnique({
+    where: {
+      id: Number(req.params.tag_id)
+    }
+  });
+  if (!tag) {
+    throw new HttpError('Tag not found', 404);
+  }
+  res.json(tag);
+};
+
+export async function get_all_of_book(req: Request, res: Response) {
+  const book = await prisma.book.findUnique({
+    where: {
+      id: Number(req.params.book_id)
+    },
+    include: {
+      tags: true
+    }
+  });
+  if (!book) {
+    throw new HttpError('Book not found', 404);
+  }
+  res.json(book.tags);
+};
+
+export async function create_one(req: Request, res: Response) {
+  assert(req.body, TagCreationData);
+  const tag = await prisma.tag.create({
+    data: req.body
+  });
+  res.status(201).json(tag);
+};
+
+export async function update_one(req: Request, res: Response) {
+  assert(req.body, TagUpdateData);
+  try {
+    const tag = await prisma.tag.update({
+      where: {
+        id: Number(req.params.tag_id)
+      },
+      data: req.body
+    });
+    res.json(tag);
+  }
+  catch (err) {
+    throw new HttpError('Tag not found', 404);
+  }
+};
+
+export async function delete_one(req: Request, res: Response) {
+  try {
+    await prisma.tag.delete({
+      where: {
+        id: Number(req.params.tag_id)
+      }
+    });
+    res.status(204).send();
+  }
+  catch (err) {
+    throw new HttpError('Tag not found', 404);
+  }
+};
+
+export async function add_one_to_book(req: Request, res: Response) {
+  await prisma.tag.update({
+    where: {
+      id: Number(req.params.tag_id)
+    },
+    data: {
+      books: {
+        connect: {
+          id: Number(req.params.book_id)
+        }
+      }
+    }
+  });
+  res.status(204).send();
+};
+
+export async function remove_one_from_book(req: Request, res: Response) {
+  await prisma.tag.update({
+    where: {
+      id: Number(req.params.tag_id)
+    },
+    data: {
+      books: {
+        disconnect: {
+          id: Number(req.params.book_id)
+        }
+      }
+    }
+  });
+  res.status(204).send();
+};
diff --git a/correction/src/validation/author.ts b/correction/src/validation/author.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4c1d758feeebb12a031b4774c3df3fc303862715
--- /dev/null
+++ b/correction/src/validation/author.ts
@@ -0,0 +1,20 @@
+import { object, string, optional, size, refine } from 'superstruct';
+import { isInt } from 'validator';
+
+export const AuthorCreationData = object({
+  firstname: size(string(), 1, 50),
+  lastname: size(string(), 1, 50),
+});
+
+export const AuthorUpdateData = object({
+  firstname: optional(size(string(), 1, 50)),
+  lastname: optional(size(string(), 1, 50)),
+});
+
+export const AuthorGetAllQuery = object({
+  lastname: optional(string()),
+  hasBooks: optional(refine(string(), 'true', (value) => value === 'true')),
+  include: optional(refine(string(), 'include', (value) => value === 'books')),
+  skip: optional(refine(string(), 'int', (value) => isInt(value))),
+  take: optional(refine(string(), 'int', (value) => isInt(value))),
+});
diff --git a/correction/src/validation/book.ts b/correction/src/validation/book.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1b46a63ac7069e86d3e490a40a68cb2b2c12c7c0
--- /dev/null
+++ b/correction/src/validation/book.ts
@@ -0,0 +1,11 @@
+import { object, string, integer, optional, size } from 'superstruct';
+
+export const BookCreationData = object({
+  title: size(string(), 1, 50),
+  publication_year: optional(integer())
+});
+
+export const BookUpdateData = object({
+  title: optional(size(string(), 1, 50)),
+  publication_year: optional(integer())
+});
diff --git a/correction/src/validation/tag.ts b/correction/src/validation/tag.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4077e2ea7c156b987b09b781ab99b5f202bd70d9
--- /dev/null
+++ b/correction/src/validation/tag.ts
@@ -0,0 +1,9 @@
+import { object, string, optional, size } from 'superstruct';
+
+export const TagCreationData = object({
+  name: size(string(), 1, 50)
+});
+
+export const TagUpdateData = object({
+  name: optional(size(string(), 1, 50))
+});