@innobdx/nuxt-crud

A Nuxt module to facilitate the creation of CRUD screens for entities
5,035
Install
npm install @innobdx/nuxt-crud
Latest Version:1.30.0
Vue:3
License:MIT
Last Updated:Aug 12, 2026
Links: npm
Maintainer: inno.bdx

Nuxt CRUD Library

npm version npm downloads License Nuxt

Une bibliothèque Nuxt 3 pour faciliter la création d'écran de gestion pour vos entités (Create, Read, Update, Delete).

Installation

npm install @innobdx/nuxt-crud

Configuration

Ajoutez le module à votre application Nuxt dans nuxt.config.ts:

export default defineNuxtConfig({
  modules: [
    '@innobdx/nuxt-crud'
  ],
  
  // Vous pouvez configurer les options du module ici
  crud: {
    // Options de base du module
    isEnabled: true,
    prefix: 'Crud',
    customFetchKey: 'useApiFetch', // Optionnel: nom de votre fonction fetch personnalisée
    
    // Configuration personnalisée pour le CRUD
    config: {
      editMode: 'DIALOG', // Mode d'édition ('DIALOG' ou autre mode supporté)
      idRegex: /^\d+$/,
      route: {
        create: 'create',
        update: 'update',
        view: 'view'
      },
      subtitle: {
        list: 'Liste',
        create: 'Création',
        update: 'Modification',
        view: 'Détails'
      },
      method: {
        create: 'POST',
        update: 'PATCH'
      },
      textValidButton: {
        create: 'AJOUTER',
        update: 'MODIFIER'
      },
      authorizedEntities: [], // Tableau vide pour autoriser toutes les entités
      dataTableOptions: {
        serverSidePagination: true,
        serverSidePaginationOptions: {
          page: { name: 'page', value: 1 },
          limit: { name: 'size', value: 10 },
          sort: { name: 'sort', value: 'id' },
          query: { name: 'query', value: '' }
        },
        itemsPerPageOptions: [
          { value: 10, title: '10' },
          { value: 25, title: '25' },
          { value: 50, title: '50' }
        ],
        loadingText: 'Chargement en cours...'
      }
    }
  }
})

Configuration des requêtes API et Authentification

La bibliothèque utilise le système de fetch standard de Nuxt et ne gère pas les URLs de base ou l'authentification. C'est à vous de configurer ces aspects dans votre application.

Configuration avec une instance de fetch personnalisée

Le module supporte l'utilisation d'une instance de fetch personnalisée pour tous ses appels API. Cela vous permet de configurer votre propre instance avec une URL de base et des en-têtes d'authentification sans affecter les autres requêtes Nuxt.

Pour configurer cette fonctionnalité:

  1. Définir une URL de base et configurer customFetchKey dans nuxt.config.ts:
export default defineNuxtConfig({
  modules: [
    '@innobdx/nuxt-crud'
  ],
  
  // Indiquer au module quelle instance de fetch personnalisée utiliser
  crud: {
    customFetchKey: '$apiFetch' // Nom de l'instance de fetch fournie par votre plugin
  },
  
  // Configuration de l'URL de base pour vos API
  runtimeConfig: {
    public: {
      apiBaseUrl: 'http://localhost:3000/api'  // URL de base de votre API
    }
  }
})
  1. Créer un plugin pour fournir votre instance de fetch personnalisée:
// plugins/apiFetch.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const { $auth } = useNuxtApp() // Si vous utilisez un plugin d'authentification
  
  // Création d'une instance de fetch dédiée aux appels API
  const apiFetch = $fetch.create({
    baseURL: config.public.apiBaseUrl,
    headers: {
      'Content-Type': 'application/json',
      // Ajoutez vos en-têtes d'authentification
      'Authorization': `Bearer ${$auth?.getAccessToken?.() || ''}`
    }
  })
  
  // Exposer l'instance de fetch pour qu'elle soit accessible
  return {
    provide: {
      apiFetch // Sera disponible comme $apiFetch
    }
  }
})

Cette approche présente plusieurs avantages:

  • Ne modifie pas le fetch global de Nuxt, donc n'affecte pas les autres requêtes système
  • Permet une séparation claire entre les requêtes API et les autres requêtes
  • Facilite la gestion des en-têtes d'authentification de manière isolée
  • Respecte le principe de séparation des responsabilités

Le module utilisera automatiquement votre instance personnalisée pour tous ses appels API internes, sans avoir à modifier son code source.

Création d'un nouveau domaine

Pour faciliter la création de nouveaux domaines, la bibliothèque inclut un script CLI interactif. Pour l'utiliser:

# Option 1: Utiliser npx
npx makecrud

# Option 2: Ajouter un script dans votre package.json
# "scripts": {
#   "make:crud": "makecrud"
# }
# Puis exécuter:
npm run make:crud

Le script vous guidera à travers la création d'un nouveau domaine en vous demandant:

  • Le nom de l'entité
  • L'endpoint API
  • Le nom de la propriété ID
  • Les titres à utiliser dans l'interface

Le script génère automatiquement la structure de dossiers et tous les fichiers nécessaires.

Structure du projet

Pour que la bibliothèque fonctionne correctement, vous devez respecter une structure spécifique avec un répertoire par entité:

📁 votre-projet/
 ┣ 📁 domains/
 ┃ ┣ 📁 product/
 ┃ ┃ ┣ 📄 index.ts                    # Exporte les éléments du domaine
 ┃ ┃ ┣ 📄 FormProduct.vue             # Composant de formulaire pour l'entité
 ┃ ┃ ┣ 📄 dataTableTemplates.const.ts # Templates pour les cellules du tableau
 ┃ ┃ ┣ 📄 defaultValue.const.ts       # Valeurs par défaut pour l'entité
 ┃ ┃ ┣ 📄 tableHeaders.const.ts       # En-têtes du tableau
 ┃ ┃ ┗ 📄 titles.const.ts             # Titres pour l'interface
 ┃ ┣ 📁 user/
 ┃ ┃ ┣ 📄 index.ts
 ┃ ┃ ┣ 📄 FormUser.vue
 ┃ ┃ ┣ 📄 dataTableTemplates.const.ts
 ┃ ┃ ┣ 📄 defaultValue.const.ts
 ┃ ┃ ┣ 📄 tableHeaders.const.ts
 ┃ ┃ ┗ 📄 titles.const.ts
 ┣ 📄 domains.ts                      # Fichier qui exporte tous les domaines
 ┣ 📁 ...

Utilisation

1. Définissez vos domaines d'entités

Pour chaque entité, vous devez créer un dossier dans domains/ avec la structure décrite ci-dessus.

la commande makecrud le fait pour vous sinon vous pouvez vous inspirez du playbook qui démontre la configuration d'un domaine utilisateur.

2. Navigation automatique

La bibliothèque génère automatiquement les routes CRUD pour vos entités, aucune page n'a besoin d'être créé.

Domaines « select-only »

Un dossier de domains/ peut n'exporter qu'un CrudSelectDomain (littéral typé, sans titles, tableHeaders ni defaultValue) destiné uniquement à alimenter un CrudSelect. Ces domaines ne sont pas routables : seuls ceux construits avec defineCrudDomain() sont injectés dans $crudDomains et servis par les routes /:entity et /:entity/:action/:id?. Naviguer vers l'endpoint d'un domaine select-only produit une erreur explicite Unknown domain "…" plutôt qu'un crash de rendu.

Le marqueur est posé par defineCrudDomain() et vérifiable via isCrudDomain(domain).

Interface utilisateur avec Vuetify

La bibliothèque utilise Vuetify pour l'interface utilisateur. Les composants CRUD s'intègrent automatiquement avec votre configuration Vuetify existante.

CrudSelect

CrudSelect charge ses options depuis le domaine passé directement au composant avec la props domain, sans declarer de sous-domaine et sans dependance au domaine courant. Ce domaine peut etre un domaine CRUD complet declaré avec defineCrudDomain<T>(), ou un CrudSelectDomain minimal reservé aux selects.

<script setup lang="ts">
import type { UserInterface as User } from '~/types/user'
import { UsersDomain } from '~/domains/users'

const selectedUser = ref<User | null>(null)

function getUserTitle(user: User) {
  return `${user.firstName} ${user.lastName}`
}
</script>

<template>
  <CrudSelect
    v-model="selectedUser"
    :domain="UsersDomain"
    label="Utilisateur"
    density="compact"
    variant="solo"
    :item-title="getUserTitle"
    :selection-title="getUserTitle"
  />
</template>

Le domaine lie reste un domaine CRUD normal :

export const UsersDomain = defineCrudDomain<User>()('id', {
  name: 'users',
  endpoint: 'users',
  responseFormat: 'envelope',
  searchEndpoint: 'search/findByFirstNameContainingIgnoreCaseOrLastNameContainingIgnoreCase',
  searchParam: ['firstName', 'lastName'],
  // ...
})

Pour une relation, le domaine parent ne declare rien de special. Il suffit que le domaine du type cible existe :

export const ProductDomain = defineCrudDomain<Product>()('id', {
  name: 'products',
  endpoint: 'products',
  responseFormat: 'envelope',
  searchEndpoint: 'search/findByNameContainingIgnoreCase',
  searchParam: 'name',
  // ...
})

Dans le formulaire utilisateur, le select cible explicitement le type voulu. Le mode multiple reste pilote par la prop multiple :

<CrudSelect
  v-model="user.product"
  :domain="ProductDomain"
  :item-title="product => product.name"
  :selection-title="product => product.name"
/>

<CrudSelect
  v-model="user.products"
  :domain="ProductDomain"
  multiple
  :item-title="product => product.name"
  :selection-title="product => product.name"
/>

domain est le domaine CRUD utilise pour charger les options du select. Il n'y a pas de fallback par nom de domaine, sous-domaine, attribut parent, type ou valeur selectionnee.

Pour une recherche serveur, il faut configurer explicitement searchEndpoint et searchParam, ou passer searchAttributes sur le composant, ou fournir customCrud.readAll. Sans configuration de recherche, saisir du texte leve une erreur explicite. Avec searchParam: string[], la meme recherche est envoyee a chaque parametre configure.

Les attributs Vuetify publics (variant, density, class, style, etc.) sont transmis au VAutocomplete. Les bindings internes (items, loading, item-title, item-value, value-comparator, disabled, search, menu) restent pilotes par CrudSelect.

Slots disponibles :

<CrudSelect
  v-model="selectedUser"
  :domain="UsersDomain"
  :item-title="getUserTitle"
  :selection-title="getUserTitle"
>
  <template #item-prepend="{ item }">
    <UserAvatar :user="item" />
  </template>

  <template #item="{ props, item, title, subtitle }">
    <VListItem v-bind="props" :title="title" :subtitle="subtitle" />
  </template>

  <template #item-append="{ item }">
    <UserStatus :user="item" />
  </template>

  <template #selection-prepend="{ item }">
    <UserAvatar :user="item" />
  </template>

  <template #selection="{ item }">
    {{ item.firstName }} {{ item.lastName }}
  </template>

  <template #selection-append="{ item }">
    <UserStatus :user="item" />
  </template>
</CrudSelect>

CrudSelect utilise les labels i18n du domaine/module pour noDataEmpty, noDataError et loadingText. La prop noData reste disponible pour surcharger localement le texte vide.

Actions personnalisées

Pour ajouter des actions personnalisées, vous pouvez étendre les templates de colonnes dans votre configuration de domaine.

Composants disponibles

  • AppCrudReadAllView: Vue pour afficher la liste des entités
  • AppCrudActionView: Vue pour les actions (créer/éditer/voir les détails)
  • AppCrudTable: Tableau pour afficher les entités
  • AppCrudTableActions: Boutons d'action pour chaque ligne
  • AppCrudTableDeleteDialog: Dialogue de confirmation de suppression
  • AppCrudTableNoData: Affichage lorsqu'il n'y a pas de données
  • AppCrudTableTitle: Titre du tableau avec boutons d'action
  • AppChip: Composant pour afficher des badges/étiquettes

Composables disponibles

  • useListEntityService: Gère la récupération et la pagination des entités
  • useFetchService: Service de base pour les requêtes API
  • useCrudFieldError(path): Erreurs serveur ciblées sur un champ (voir « Gestion des erreurs »)
  • useCrudFieldErrorMessage(path): Premier message d'erreur pour un champ
  • useCrudFieldErrorMessages(path): Liste de messages, prête à brancher sur Vuetify :error-messages

Gestion des erreurs

Les erreurs réseau (FetchError ofetch) sont normalisées automatiquement en CrudNetworkError typé. Plus besoin de configurer un chemin (errorPath) pour extraire le message : le module reconnaît les conventions courantes (RFC 7807, Spring, Laravel, payloads ad-hoc).

Affichage par défaut

Une erreur de soumission affiche un <v-alert role="alert" aria-live="polite"> avec error.message. Le slot #error de CrudActionView / CrudFormLayout permet de surcharger l'affichage :

<CrudActionView :domain="UserDomain" :item="user" action="create">
  <template #error="{ error, fieldErrors }">
    <v-alert type="error" :text="error.message" />
    <ul v-if="fieldErrors.length">
      <li v-for="(fe, i) in fieldErrors" :key="i">{{ fe.path.join('.') }}: {{ fe.message }}</li>
    </ul>
  </template>
</CrudActionView>

Vous pouvez aussi définir errorComponent sur le domaine pour fournir un composant réutilisable.

Erreurs par champ (server-side validation)

Si le backend renvoie un 422 au format Spring/Laravel :

{ "message": "Validation failed", "errors": { "username": ["This username already exists"] } }

…ou au format RFC 7807 (errors: [{ pointer: "/username", detail: "..." }]), les erreurs sont automatiquement converties en FieldError[] et exposées au composant de formulaire via useCrudFieldError :

<script setup lang="ts">
import { useCrudFieldErrorMessages } from '#crud/composables/useCrudFieldError'

const usernameServerErrors = useCrudFieldErrorMessages('username')
// ou : useCrudFieldErrorMessages(['address', 'zip'])
// ou : useCrudFieldErrorMessages('items[0].name')
</script>

<template>
  <VTextField
    v-model="user.username"
    label="Username"
    :error-messages="usernameServerErrors"
  />
</template>

Les erreurs Zod issues du schema du domaine sont également converties en FieldError[] et propagées via le même mécanisme.

Format FieldError

interface FieldError {
  path: (string | number)[]   // ex: ['address', 'zip'], ['items', 0, 'name']
  message: string
  code?: string               // optionnel (Zod issue.code, RFC 7807 code, ...)
}

Compatible Standard Schema.

Backends non-standard : errorAdapter

Pour un backend exotique, fournissez un adapter via un plugin Nuxt — il sera prioritaire sur les heuristiques internes :

// plugins/crud-error-adapter.ts
import type { ErrorAdapter } from '@innobdx/nuxt-crud'

export default defineNuxtPlugin(() => ({
  provide: {
    crudErrorAdapter: ((err: any) => {
      if (err?.data?.legacyError) {
        return {
          message: err.data.legacyError.userMessage,
          status: err.statusCode,
          fieldErrors: err.data.legacyError.fields?.map((f: any) => ({
            path: f.field.split('.'),
            message: f.reason,
          })),
        }
      }
      return undefined // fallback aux heuristiques internes
    }) satisfies ErrorAdapter,
  },
}))

Hiérarchie d'erreurs exposées

Classe Cas
CrudError Base — domain, cause, fieldErrors?
CrudNetworkError Erreur HTTP — status, data, fieldErrors?
CrudValidationError Échec Zod côté client — zodError, fieldErrors
CrudNotFoundError 404 entité — id

Tous sont exportés depuis @innobdx/nuxt-crud.

Licence

MIT

Related Packages

@innobdx/nuxt-oidc-auth

Module d'authentification OIDC pour Nuxt 4

2,618

Version History

Version Released License
1.30.0 MIT
1.29.0 MIT
1.28.0 MIT
1.27.0 MIT
1.25.0 MIT