FridgeCook API est une API REST construite avec NestJS, Prisma et PostgreSQL pour gérer des recettes, ingrédients et régimes alimentaires. L'API utilise une approche "Hybrid" : relationnelle pour les connexions/logique, JSONB pour le contenu multilingue.
Stack technique :
- Framework: NestJS 11
- ORM: Prisma
- Database: PostgreSQL
- Authentication: À implémenter
- Versioning: API v1
http://localhost:3000
interface TranslatableString {
fr: string;
en: string;
}interface Step {
stepNumber: number;
instruction: TranslatableString;
imageUrl?: string;
}- Recipe : Recette principale
- Ingredient : Ingrédient
- Diet : Tags de régime (Vegan, Keto, etc.)
- RecipeIngredient : Table pivot (recette ↔ ingrédient)
Préalable : Les ingrédients et régimes doivent d'abord être créés (voir sections 2 et 3).
POST /recipes
Content-Type: application/json
{
"slug": "pate-carbo",
"title": {
"fr": "Pâtes à la Carbonara",
"en": "Pasta Carbonara"
},
"description": {
"fr": "Recette italienne classique",
"en": "Classic Italian recipe"
},
"steps": [
{
"stepNumber": 1,
"instruction": {
"fr": "Faire bouillir l'eau",
"en": "Boil water"
},
"imageUrl": "https://example.com/step1.jpg"
},
{
"stepNumber": 2,
"instruction": {
"fr": "Cuire les pâtes",
"en": "Cook pasta"
}
}
],
"prepTime": 10,
"cookingTime": 20,
"servings": 4,
"difficulty": 2,
"ingredientIds": ["550e8400-e29b-41d4-a716-446655440001", "550e8400-e29b-41d4-a716-446655440002"],
"dietIds": ["550e8400-e29b-41d4-a716-446655440003"]
}Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "pate-carbo",
"title": {
"fr": "Pâtes à la Carbonara",
"en": "Pasta Carbonara"
},
"description": {
"fr": "Recette italienne classique",
"en": "Classic Italian recipe"
},
"steps": [...],
"prepTime": 10,
"cookingTime": 20,
"servings": 4,
"difficulty": 2,
"recipeIngredients": [
{
"id": "uuid",
"recipeId": "...",
"ingredientId": "...",
"quantity": 0,
"unit": "",
"ingredient": {...}
}
],
"diets": [...],
"createdAt": "2026-01-08T12:00:00.000Z",
"updatedAt": "2026-01-08T12:00:00.000Z"
}GET /recipes?skip=0&take=10Query Parameters:
skip: Nombre d'éléments à ignorer (défaut: 0)take: Nombre d'éléments à retourner (défaut: 10)
Response (200):
{
"data": [
{
"id": "...",
"slug": "pate-carbo",
"title": {...},
"description": {...},
"steps": [...],
"prepTime": 10,
"cookingTime": 20,
"servings": 4,
"difficulty": 2,
"recipeIngredients": [...],
"diets": [...],
"createdAt": "...",
"updatedAt": "..."
}
],
"total": 42,
"skip": 0,
"take": 10
}GET /recipes/:idParameters:
id: UUID de la recette
Response (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "pate-carbo",
...
}Error (404):
{
"statusCode": 404,
"message": "Recipe not found"
}GET /recipes/slug/:slugParameters:
slug: Slug unique de la recette
Response (200): Même que 1.3
PUT /recipes/:id
Content-Type: application/json
{
"title": {
"fr": "Pâtes Carbonara Revisitée",
"en": "Revisited Pasta Carbonara"
},
"difficulty": 3,
"prepTime": 15
}Note: Tous les champs sont optionnels
Response (200): Recette modifiée
DELETE /recipes/:idResponse (204): Pas de contenu
POST /recipes/:recipeId/ingredients
Content-Type: application/json
{
"ingredientId": "550e8400-e29b-41d4-a716-446655440000",
"quantity": 500,
"unit": "g"
}Response (201):
{
"id": "uuid",
"recipeId": "...",
"ingredientId": "...",
"quantity": 500,
"unit": "g",
"ingredient": {
"id": "...",
"slug": "tomate",
"name": {
"fr": "Tomate",
"en": "Tomato"
},
"iconUrl": "https://..."
}
}PUT /recipes/:recipeId/ingredients/:ingredientId
Content-Type: application/json
{
"quantity": 600,
"unit": "ml"
}Response (200): Ingrédient modifié
DELETE /recipes/:recipeId/ingredients/:ingredientIdResponse (204): Pas de contenu
POST /ingredients
Content-Type: application/json
{
"slug": "tomate",
"name": {
"fr": "Tomate",
"en": "Tomato"
},
"iconUrl": "https://example.com/tomate.png"
}Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "tomate",
"name": {
"fr": "Tomate",
"en": "Tomato"
},
"iconUrl": "https://example.com/tomate.png",
"createdAt": "2026-01-08T12:00:00.000Z",
"updatedAt": "2026-01-08T12:00:00.000Z"
}GET /ingredients?skip=0&take=10Query Parameters:
skip: Nombre d'éléments à ignorer (défaut: 0)take: Nombre d'éléments à retourner (défaut: 10)
Response (200):
{
"data": [
{
"id": "...",
"slug": "tomate",
"name": {...},
"iconUrl": "...",
"recipeIngredients": [...]
}
],
"total": 50,
"skip": 0,
"take": 10
}GET /ingredients/:idResponse (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "tomate",
"name": {...},
"iconUrl": "...",
"recipeIngredients": [
{
"id": "...",
"recipeId": "...",
"ingredientId": "...",
"quantity": 500,
"unit": "g",
"recipe": {...}
}
]
}GET /ingredients/slug/:slugResponse (200): Même format que 2.3
PUT /ingredients/:id
Content-Type: application/json
{
"name": {
"fr": "Tomate cerise",
"en": "Cherry tomato"
}
}Response (200): Ingrédient modifié
DELETE /ingredients/:idResponse (204): Pas de contenu
POST /diets
Content-Type: application/json
{
"slug": "vegan",
"name": {
"fr": "Végan",
"en": "Vegan"
}
}Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "vegan",
"name": {
"fr": "Végan",
"en": "Vegan"
},
"createdAt": "2026-01-08T12:00:00.000Z",
"updatedAt": "2026-01-08T12:00:00.000Z"
}GET /diets?skip=0&take=10Response (200):
{
"data": [
{
"id": "...",
"slug": "vegan",
"name": {...},
"recipes": [...]
}
],
"total": 15,
"skip": 0,
"take": 10
}GET /diets/:idResponse (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "vegan",
"name": {...},
"recipes": [
{
"id": "...",
"slug": "pate-carbo",
"title": {...},
...
}
]
}GET /diets/slug/:slugResponse (200): Même format que 3.3
PUT /diets/:id
Content-Type: application/json
{
"name": {
"fr": "Végan strict",
"en": "Strict Vegan"
}
}Response (200): Régime modifié
DELETE /diets/:idResponse (204): Pas de contenu
200 OK: Requête réussie201 Created: Ressource créée204 No Content: Succès, pas de contenu
400 Bad Request: Validation échouée404 Not Found: Ressource non trouvée500 Internal Server Error: Erreur serveur
curl -X POST http://localhost:3000/ingredients \
-H "Content-Type: application/json" \
-d '{
"slug": "oeufs",
"name": {"fr": "Oeufs", "en": "Eggs"},
"iconUrl": "https://example.com/eggs.png"
}'
# Réponse: {"id": "550e8400-e29b-41d4-a716-446655440001", ...}curl -X POST http://localhost:3000/diets \
-H "Content-Type: application/json" \
-d '{
"slug": "classique",
"name": {"fr": "Classique", "en": "Classic"}
}'
# Réponse: {"id": "550e8400-e29b-41d4-a716-446655440003", ...}curl -X POST http://localhost:3000/recipes \
-H "Content-Type: application/json" \
-d '{
"slug": "pate-carbo",
"title": {"fr": "Pâtes à la Carbonara", "en": "Pasta Carbonara"},
"description": {"fr": "Recette italienne", "en": "Italian recipe"},
"steps": [{"stepNumber": 1, "instruction": {"fr": "Cuire les pâtes", "en": "Cook pasta"}}],
"prepTime": 10,
"cookingTime": 20,
"servings": 4,
"difficulty": 2,
"ingredientIds": ["550e8400-e29b-41d4-a716-446655440001"],
"dietIds": ["550e8400-e29b-41d4-a716-446655440003"]
}'curl http://localhost:3000/recipes?skip=0&take=5curl http://localhost:3000/recipes/slug/pate-carbo# D'abord créer un nouvel ingrédient (si nécessaire)
INGREDIENT_ID=$(curl -s -X POST http://localhost:3000/ingredients \
-H "Content-Type: application/json" \
-d '{"slug": "bacon", "name": {"fr": "Bacon", "en": "Bacon"}}' | jq -r '.id')
# Puis l'ajouter à la recette
curl -X POST http://localhost:3000/recipes/{recipeId}/ingredients \
-H "Content-Type: application/json" \
-d "{
\"ingredientId\": \"$INGREDIENT_ID\",
\"quantity\": 200,
\"unit\": \"g\"
}"slug: string, uniquetitle: TranslatableStringsteps: Step[]prepTime: number (minutes)cookingTime: number (minutes)servings: numberdifficulty: number (1-5 recommandé)
slug: string, uniquename: TranslatableString
slug: string, uniquename: TranslatableString
L'API retourne une réponse JSON en cas d'erreur :
{
"statusCode": 400,
"message": "Slug must be unique",
"error": "Bad Request"
}Pour toutes les listes (/recipes, /ingredients, /diets) :
skip: Nombre de résultats à sauter (défaut: 0)take: Nombre de résultats à retourner (défaut: 10, max: 100)
Exemple :
GET /recipes?skip=20&take=10
Retourne les résultats 21-30.
Toutes les entités incluent :
createdAt: Date/heure de création (ISO 8601)updatedAt: Date/heure de dernière modification (ISO 8601)
Relation OneToMany avec CASCADE DELETE. Supprimer une recette supprime tous ses ingrédients.
Relation ManyToMany via table recipe_diets.
Relation ManyToMany avec eager loading.
model Recipe {
id String @id @default(uuid()) @db.Uuid
slug String @unique
title Json @db.JsonB
description Json? @db.JsonB
steps Json @db.JsonB
prepTime Int
cookingTime Int
servings Int
difficulty Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
recipeIngredients RecipeIngredient[]
diets Diet[]
}# Installation
npm install
# Migration de base de données
npx prisma migrate dev
# Développement
npm run dev
# Production
npm run build
npm run start:prod- Authentification (JWT)
- Autorisations (RBAC)
- Validation avec class-validator
- Tests unitaires
- Documentations Swagger/OpenAPI
- Rate limiting
- Caching
- Search full-text sur recettes
- Ratings et commentaires
- Favoris utilisateur
Pour toute question ou bug, contactez l'équipe de développement.