Overview

One decorated model and one configuration object produce both halves of a feature: the list and item screens in the browser, and the REST endpoints that serve them.


What the family generates

The crud packages are a container layer, not a component library. Nothing about a screen is written by hand: the entity is described with decorators, the behaviour of its screens is described with a configuration object, and the engine composes the generated UI out of the free components of @smartsoft001/angular.

On the frontend that yields two page components. smart-crud-list-page renders the collection with its search box, filters, pagination, sorting, selection and export. smart-crud-item-page renders one record in create, details or update mode. Neither takes an input: both read the configuration that was provided for the feature.

On the backend the same description yields a generic controller. With restApi enabled, CrudShellNestjsModule.forRoot registers create, bulk create, read by id, read a filtered page, replace, patch and delete, all guarded by the JWT strategy and the permission map. Architecture lists the routes.

The three inputs

Every generated screen is the product of three things, and nothing else.

InputWhere it livesWhat it decides
Decorators@Model and @Field from @smartsoft001/modelsWhat each field is: its type, the editor it gets, its validation, and which operations may touch it.
ConfigurationA CrudFullConfig<T> provided per featureWhat the screens do with those fields: the endpoint, the buttons, search, export, paging, sorting.
EngineCreateDynamicComponent and the page componentsWhich child components are instantiated at runtime from the first two.

Because the engine reads metadata rather than markup, one pair of page components serves every entity in an application, and a screen changes by changing the description.


Wiring the frontend

A feature module registers the CRUD slice with CrudModule.forFeature.

import { NgModule } from '@angular/core';

import { CrudModule } from '@smartsoft001/crud-shell-angular';

import { noteCrudConfig } from './crud-config.example';

// `routing: false` registers the CRUD core module only: the NgRx slice, the
// services and the components. The app places `<smart-crud-list-page>` and
// `<smart-crud-item-page>` itself. Use `routing: true` to get the generated
// list/add/:id routes instead.
export const notesCrudFeature = CrudModule.forFeature({
  routing: false,
  config: noteCrudConfig,
});

@NgModule({
  imports: [notesCrudFeature],
})
export class NotesModule {}

forFeature returns the core module when routing is false and the full module when it is true. Either way it provides the configuration under both CrudConfig and CrudFullConfig, derives FILE_SERVICE_CONFIG from apiUrl so attachment fields resolve their URLs, and registers either the real socket service or an inert stub depending on socket. The module constructor then registers the entity's reducer and starts the effects. With routing: true the full module also adds three child routes: the empty path renders the list page, add renders the item page in create mode, and :id renders it for one record.

Root injector prerequisites

The feature module is not self-contained. NgRx has to be present at the application injector before any CRUD feature is imported, because Actions, EffectSources and EffectsRunner are provided in root and resolve their Store there.

RegistrationWhy it is required
StoreModule.forRoot({})The feature reducer is added to this store at runtime, keyed by entity.
EffectsModule.forRoot([])The CRUD effects call init() from the module constructor and need the root effects runner.
NgrxSharedModuleConnects the static store reference that forFeature uses to register the reducer.
TranslateModule.forRoot()Labels resolve through MODEL.<key> translation keys; without it the raw keys are rendered.
RouterModule.forRoot(…)The pages navigate between the list and the item routes.

Installation covers the shared Angular providers that sit alongside these.

Wiring the backend

The server side takes one module import, configured once per feature.

import { Module } from '@nestjs/common';

import { CrudShellNestjsModule } from '@smartsoft001/crud-shell-nestjs';

import { Note } from './crud-service.example';

@Module({
  imports: [
    CrudShellNestjsModule.forRoot({
      // The JWT secret guards the REST routes. It must not be empty when
      // `restApi` is on, because the passport strategy is built eagerly.
      tokenConfig: {
        secretOrPrivateKey: 'change-me',
        expiredIn: 3600,
      },
      permissions: {
        create: ['admin'],
        read: ['admin', 'user'],
        update: ['admin'],
        delete: ['admin'],
      },
      // The connection is opened lazily, on the first query.
      db: {
        host: 'localhost',
        port: 27017,
        database: 'my-app',
        collection: 'notes',
        // The model class. `CrudService` turns every request body into an
        // instance of it before validating, so without it a body missing a
        // required field would be stored as sent.
        type: Note,
      },
      restApi: true,
      socket: false,
    }),
  ],
})
export class NotesModule {}

tokenConfig feeds the JWT strategy that guards the write routes, permissions maps each operation to the roles allowed to perform it, and db is the MongoDB collection the repository reads. The apiUrl in the Angular configuration points at wherever this module is mounted.

What the details mode does

Setting details on the configuration makes the rows of the list open the record on its own route, where the item page renders it read-only. The list itself never shows an inline detail panel, so use edit as well when the record also has to be editable.


Where each concern lives

PackageRole
crud-domainThe entities and business rules, free of any framework.
crud-shell-angularThe pages, filter widgets, export and multiselect components, the NgRx slice and the facade.
crud-shell-nestjsThe generic controller, the websocket gateway and the auth guards.
crud-shell-dtosThe shapes that cross the network.
crud-shell-app-servicesThe application services both shells resolve from their injector.

Where next