Skip to content

Golden Path — Service (*.service.ts)

The service is where a datatype’s CRUD and domain rules live. Routes should stay thin and call into the service so the same logic can be reused from jobs, other routes, or tests without duplicating HTTP concerns.

Typical shape for a datatype

For one persisted concept you usually end up with:

  • Read paths — list and get-by-id (and variants: search, filters).
  • Write paths — create, update, soft-delete, hard-delete, move, … as your domain needs.

Raclette does not mandate method names, but we recommend a clear split (this ties directly to plugin contracts and cross-plugin use — see Cross-plugin communication).

Internal (“raw”) methods

Often named with a leading underscore (for example _read, _create, _update): they talk to Mongoose (or future drivers) and return plain documents or lean results. They are the persistence layer of the datatype.

Public (“API”) methods

No underscore: they orchestrate validation, call internal methods, build payload-compatible results, and emit side effects (logging, channels, …) expected by the rest of the stack.

That split keeps “what the database returned” separate from “what we return to HTTP and the frontend pipeline”.

Why this matters for the frontend

Public methods are what routes should return so $data, sockets, and workbench stay coherent. The bridge is the payload layer: each datatype registers how items become payload rows. Read the dedicated guide:

Plugin contracts and predictable “CRUD-shaped” services

If you expose a plugin contract (see Plugin contracts and extension points), the service is usually the right place to attach the methods other plugins will call. Contract method names are entirely your choice, but guessable naming — especially shapes that resemble list / get / create / update / delete — pays off twice:

  • other plugins can integrate without bespoke adapters for every partner plugin, and
  • generic tooling (search, automation, generated UIs) has a better chance of doing the right thing when conventions match expectations.

This is a recommendation, not a hard rule: exotic domains may need exotic method names. When they do, document the contract surface explicitly.

Cross-plugin communication

Contracts are the main typed, in-process bridge between plugins: register once, consume by contract id from another plugin’s backend code.

  • Prefer stable, boring names on the contract object when the domain is generic (entities, folders, jobs).
  • Keep HTTP routes as the user-facing or remote API; keep contracts for trusted same-process composition (orchestration, shared registries).
  • Align contract verbs with service public methods where possible so your own routes, tests, and external consumers see one coherent story.

Payload and route config.type / broadcastChannels are separate concerns (UI store and realtime); they complement contracts rather than replace them.

Snippet (service construction from models)

typescript
const exampleService = createExampleService(models.example)

See also