Modéliser commandes et lignes de commande

Structurez les commandes et les lignes de commande pour suivre les achats des utilisateurs.

4 min read

Introduction

Dans cette leçon, tu vas apprendre à modéliser deux entités clés d’un site e-commerce : les commandes et leurs lignes de commande. L’objectif est de créer des collection types Strapi qui garantissent :

  • l’intégrité des données (prix, quantités, statuts),
  • la relation entre commandes et lignes,
  • la liaison avec l’utilisateur et les produits.

Ceci servira de fondation à la gestion du panier, à la génération des factures, et à l’historique des achats dans ton app Remix + Strapi 5 headless.

Modélisation dans Strapi

Définir le collection type commande

On commence par le fichier de schéma Strapi. Il se trouve typiquement dans
src/api/commande/content-types/commande/schema.json.

src/api/commande/content-types/commande/schema.json
1
{
2
"kind": "collectionType",
3
"collectionName": "commandes",
4
"info": {
5
"singularName": "commande",
6
"pluralName": "commandes",
7
"displayName": "Commande"
8
},
9
"options": {
10
"draftAndPublish": false
11
},
12
"attributes": {
13
"totalPrice": {
14
"type": "integer"
15
},
16
"lines": {
17
"type": "relation",
18
"relation": "oneToMany",
19
"target": "api::ligne-de-commande.ligne-de-commande"
20
},
21
"user": {
22
"type": "relation",
23
"relation": "manyToOne",
24
"target": "plugin::users-permissions.user",
25
"inversedBy": "orders"
26
},
27
"orderStatus": {
28
"type": "enumeration",
29
"enum": ["en attente de paiement","payé"],
30
"default": "en attente de paiement",
31
"required": true
32
}
33
}
34
}
  • totalPrice stocke la somme de toutes les lignes.
  • lines crée une relation 1⇾n vers ligne-de-commande.
  • user relie chaque commande à un utilisateur Strapi natif.
  • orderStatus est un enum qui contrôle le flux de paiement.

Définir le collection type ligne-de-commande

Ensuite, on modélise chaque article acheté. Le fichier est
src/api/ligne-de-commande/content-types/ligne-de-commande/schema.json.

src/api/ligne-de-commande/content-types/ligne-de-commande/schema.json
1
{
2
"kind": "collectionType",
3
"collectionName": "ligne_de_commandes",
4
"info": {
5
"singularName": "ligne-de-commande",
6
"pluralName": "ligne-de-commandes",
7
"displayName": "Ligne de commande"
8
},
9
"options": {
10
"draftAndPublish": true
11
},
12
"attributes": {
13
"produit": {
14
"type": "relation",
15
"relation": "oneToOne",
16
"target": "api::produit.produit"
17
},
18
"quantity": {
19
"type": "integer",
20
"default": 1,
21
"required": true
22
},
23
"price": {
24
"type": "integer",
25
"default": 0
26
}
27
}
28
}
  • produit est un lien 1⇾1 vers ton contenu produit.
  • quantity et price permettent de conserver l’historique, même si le prix du produit change plus tard.
  • draftAndPublish est activé pour pouvoir ébaucher des lignes pendant le processus de checkout.

Relier les deux modèles

La relation oneToMany sur commande.lines s’appuie sur la clé étrangère générée dans ligne-de-commande. Strapi synchronise automatiquement les IDs.
En REST, tu pourras obtenir une commande complète avec :

Terminal
1
GET /api/commandes?populate=lines.produit,user
  • populate garantit que tes données associées (produit, user) sont incluses.

Intégration dans Remix

Pour exploiter ces modèles dans ton app Remix, crée des types TypeScript puis un loader :

src/types/commande.ts
1
export interface LigneCommande {
2
id: number
3
produit: { id: number; name: string; price: number }
4
quantity: number
5
price: number
6
}
7
8
export interface Commande {
9
id: number
10
totalPrice: number
11
orderStatus: "en attente de paiement" | "payé"
12
lines: LigneCommande[]
13
user: { id: number; username: string }
14
}
app/routes/commandes/$id.tsx
1
import type { LoaderArgs } from "@remix-run/node"
2
import { json } from "@remix-run/node"
3
4
export const loader = async ({ params, request }: LoaderArgs) => {
5
const res = await fetch(
6
`${process.env.API_URL}/commandes/${params.id}?populate=lines.produit,user`
7
)
8
const data = await res.json()
9
return json<Commande>(data.data)
10
}
app/routes/commandes/$id.tsx
1
import { useLoaderData } from "@remix-run/react"
2
3
export default function CommandePage() {
4
const commande = useLoaderData<Commande>()
5
return (
6
<div>
7
<h2>Détails de la commande #{commande.id}</h2>
8
<ul>
9
{commande.lines.map((line) => (
10
<li key={line.id}>
11
{line.produit.name} × {line.quantity} = {line.price}
12
</li>
13
))}
14
</ul>
15
<p>Total : {commande.totalPrice} </p>
16
<p>Status : {commande.orderStatus}</p>
17
</div>
18
)
19
}

Points clés

  • Utiliser enum pour contrôler les statuts et éviter les chaînes libres.
  • Préférer draftAndPublish: false pour les commandes en production.
  • Calculer totalPrice côté serveur pour fiabilité.
  • populate permet de récupérer les relations en un seul appel.
  • Définir des types TS pour exploiter la puissance du typage en front.

Exercices

  1. Ajouter un statut “expédié”
    Mets à jour l’énumération orderStatus pour inclure "expédié"
    puis teste le endpoint REST pour valider ton schéma.

  2. Appliquer une remise
    Ajoute un champ discountCode (relation vers un nouveau promo-code) et un champ discountAmount.
    Modifie le totalPrice pour en tenir compte.

  3. Page utilisateur
    Crée une route Remix /mon-compte/commandes
    qui liste toutes les commandes de l’utilisateur connecté.
    Filtre via ?filters[user][id][$eq]=:userId.


Pour aller plus loin, explore la leçon suivante sur la gestion du panier en React Context.