Skip to main content

Dictionary Service with NestJS

Build a typed dictionary service that separates reference data (catalogs, FAQs, categories) from your transactional database — with schema migrations, auto-generated clients, and CI/CD deployment.

Stack used in this guide

This guide uses NestJS backend, Kubernetes deployment, and GitHub Actions CI/CD. The pattern itself is stack-agnostic — adapt it to any language, framework (Express, Fastify, Django, Spring), container orchestrator, or even a monolith with docker-compose.

Revisium runs in two modes:

  • Local developmentnpx @revisium/standalone with embedded PostgreSQL, zero config
  • Staging / Production — either a self-hosted Revisium instance (Docker, K8s) or Revisium Cloud

The architecture stays the same — only the Revisium URL changes.

The Problem

Backend services typically store two fundamentally different types of data in the same database:

  • Transactional — users, orders, sessions. Created by business logic at runtime. Managed via ORM migrations.
  • Reference — FAQs, categories, catalogs, configurations. Edited by content managers or AI agents. Changes rarely, read often.

Keeping reference data in your main database creates friction:

  • Content managers can't edit data without developer involvement
  • Reference data migrations clutter your business migration history
  • No visual UI for managing entries
  • No versioning — can't roll back a bad content change
  • No review process for content updates

The Solution

Use Revisium as a dedicated microservice for reference data. It provides Admin UI, versioning, REST/GraphQL APIs with OpenAPI spec — out of the box. No need to build CRUD, migrations, or admin panels.

Your backend calls Revisium through a typed REST client generated from the endpoint's OpenAPI spec.

Multi-Project Architecture

Think of each Revisium project as a ready-made microservice for a specific data domain. You define the schema, and Revisium gives you a versioned REST/GraphQL API with OpenAPI spec, Admin UI, and migrations — in minutes, not days.

Each domain gets its own project, endpoint, and generated client. Domains are versioned independently.

ProjectTablesRelationships
faqFaqCategory, FaqItemFaqItem → FaqCategory (FK)
catalogBrand, ProductCategory, ProductProduct → Brand (FK), Product → ProductCategory (FK)

Need another reference domain? Create a new project, define the schema, generate a client — done.

Prerequisites

  • Node.js 18+
  • An existing NestJS project (or create one with nest new)
  • npx @revisium/standalone@latest (no global install needed)

Step 1: Start Revisium (Local Development)

For local development, use the standalone mode — it bundles everything (API, Admin UI, embedded PostgreSQL) in a single process:

npx @revisium/standalone@latest

Open Admin UI at http://localhost:9222.

Production environments

On staging and production, you'll point your backend at a self-hosted Revisium instance (e.g., deployed via Helm chart to your K8s cluster) or at Revisium Cloud. The integration code is identical — only the REVISIUM_URL environment variable changes. See Step 6: Deploy for details.

Step 2: Design Your Schema

In the Admin UI:

  1. Create a project (e.g., faq)
  2. Create tables (FaqCategory, FaqItem) — see Table Editor
  3. Define schemas using JSON Schema in the Schema Editor:

FaqCategory:

{
"type": "object",
"properties": {
"name": { "type": "string", "default": "" },
"slug": { "type": "string", "default": "" }
},
"required": ["name", "slug"]
}

FaqItem (with a foreign key to FaqCategory):

{
"type": "object",
"properties": {
"question": { "type": "string", "default": "" },
"answer": { "type": "string", "default": "", "contentMediaType": "text/markdown" },
"order": { "type": "number", "default": 0 },
"categoryId": { "type": "string", "default": "", "foreignKey": "FaqCategory" }
},
"required": ["question", "answer", "order", "categoryId"]
}
  1. Commit the revision

Step 3: Create Endpoint and Generate Client

Create the endpoint

In Admin UI → Project → Endpoints, enable the REST API endpoint. Revisium generates an API with OpenAPI spec for each revision of your project.

Two built-in endpoints are available per branch:

  • Head — last committed revision, immutable and read-only (production-ready)
  • Draft — current working revision, writable (includes uncommitted changes)

You can also create custom endpoints pinned to a specific revision for stable integrations.

Which one to use depends on your project: Head is typical for production clients and CI/CD code generation. Draft is useful during development or when your app needs to read and write data before committing.

The URL pattern is:

/endpoint/openapi/{org}/{project}/{branch}/head/openapi.json     # OpenAPI spec (head)
/endpoint/openapi/{org}/{project}/{branch}/draft/openapi.json # OpenAPI spec (draft)
/endpoint/swagger/{org}/{project}/{branch}/head # Swagger UI (head)
/endpoint/swagger/{org}/{project}/{branch}/draft # Swagger UI (draft)

Generate a typed client

Since Revisium provides a standard OpenAPI spec, you can use any OpenAPI code generator for your language:

Language / FrameworkGeneratorLink
TypeScript / Node.js@hey-api/openapi-tsdocs
TypeScript / Node.jsopenapi-typescriptdocs
Pythonopenapi-python-clientGitHub
Gooapi-codegenGitHub
Java / KotlinOpenAPI Generatordocs
Any languageOpenAPI Generatordocs

This guide uses @hey-api/openapi-ts for NestJS:

npm install -D @hey-api/openapi-ts @hey-api/client-fetch

Add an npm script to package.json:

{
"scripts": {
"generate:faq-api": "npx @hey-api/openapi-ts -i http://localhost:9222/endpoint/openapi/<org>/<project>/<branch>/head/openapi.json -o src/features/faq/generated -c @hey-api/client-fetch"
}
}

Run it:

npm run generate:faq-api

This generates fully typed functions like getFaqItems(), getFaqItemById(), etc. See Generated APIs for more details on the available API operations.

Step 4: Integrate in NestJS

The examples below use the CQRS pattern with NestJS — a common approach for separating read and write operations. You can use controllers, resolvers, or any other pattern that fits your project.

Create a service that wraps the generated client:

import { Injectable } from '@nestjs/common';
import { getFaqItems, getFaqItemById } from './generated';

@Injectable()
export class FaqApiService {
async getAll() {
const { data } = await getFaqItems();
return data;
}

async getById(id: string) {
const { data } = await getFaqItemById({ path: { id } });
return data;
}
}

Use it in a CQRS query handler:

@QueryHandler(GetFaqQuery)
export class GetFaqHandler implements IQueryHandler<GetFaqQuery> {
constructor(private readonly faqApi: FaqApiService) {}

async execute(query: GetFaqQuery) {
return this.faqApi.getAll();
}
}

Step 5: Save Migrations and Seed Data

The CLI commands below use the Revisium CLI. Install it as a dev dependency:

npm install -D @revisium/cli

Migrations

Save your schema as a migration file — this is what gets deployed to other environments:

npm run revisium:save-migrations
# → npx revisium migrate save \
# --url revisium://admin:admin@localhost:9222/admin/faq/master/draft \
# --file ./revisium/migrations.json

The Revisium URL encodes everything: credentials, host, organization, project, branch, and revision. On localhost, auth defaults to admin:admin.

Seed Data (Optional)

If your app needs baseline data on every environment (default categories, initial FAQ entries), export it:

npm run revisium:save-data
# → npx revisium rows save \
# --url revisium://admin:admin@localhost:9222/admin/faq/master/head \
# --folder ./revisium/data

Seed data is stored as JSON files — folders are tables, files are rows:

revisium/data/
├── FaqCategory/
│ ├── payment.json # rowId = "payment"
│ └── delivery.json
└── FaqItem/
├── faq-1.json # rowId = "faq-1"
└── faq-2.json

Commit both migrations.json and data/ to Git.

Step 6: Deploy

Where Revisium Runs

EnvironmentRevisium ModeDescription
Local devnpx @revisium/standaloneEmbedded PostgreSQL, zero config. Each developer runs their own instance
Staging / ProductionSelf-hosted (Docker, K8s)Revisium as a microservice in your cluster, own PostgreSQL
Staging / ProductionRevisium CloudManaged instance, no infrastructure to maintain

Your backend code doesn't change between modes — only the REVISIUM_URL environment variable:

# Local
REVISIUM_URL=http://localhost:9222

# Self-hosted (K8s internal service)
REVISIUM_URL=http://revisium.revisium.svc.cluster.local:8080

# Revisium Cloud
REVISIUM_URL=https://your-org.cloud.revisium.io

Developer Onboarding

A new developer clones the repo and gets a working Revisium with all schemas and data:

git clone <repo> && cd <repo> && npm install

# Start Revisium (empty DB on first run)
npx @revisium/standalone@latest

# Apply migrations + load seed data
npm run revisium:seed
# → npx revisium migrate apply --url revisium://admin:admin@localhost:9222/admin/faq/master --file ./revisium/migrations.json --commit
# → npx revisium rows upload --url revisium://admin:admin@localhost:9222/admin/faq/master/draft --folder ./revisium/data --commit

# Start backend
npm run start:dev

CI/CD: Applying Migrations on Deploy

Migrations are baked into the Docker image and applied idempotently at startup — same pattern as Prisma:

In package.json:

{
"scripts": {
"start:prod": "npm run revisium:seed && node dist/src/main"
}
}

Migrations are idempotent — safe to run on every container restart.

Production Architecture (Self-Hosted, K8s Example)

This is one possible deployment topology. You can adapt it to docker-compose, ECS, or any container runtime.

Using Revisium Cloud instead?

Skip the Revisium infrastructure entirely. Point your backend at your Cloud URL, and content managers use the hosted Admin UI. Migrations are applied via CLI against the Cloud API — same revisium:seed command, different URL.

What Lives in Git

your-project/
├── revisium/
│ ├── migrations.json ← schemas + structure
│ └── data/ ← seed data (optional)
│ ├── FaqCategory/
│ │ └── payment.json
│ └── FaqItem/
│ └── faq-1.json
├── .revisium/ ← .gitignore (local embedded DB)
└── src/features/
├── faq/
│ ├── generated/ ← typed client from endpoint
│ └── faq-api.service.ts
└── catalog/
├── generated/
└── catalog-api.service.ts

Workflow: Changing the Schema

See also: Schema Editor for editing schemas, Changes & Diff for reviewing changes before commit, Migrations for the full migration workflow.

Revisium vs Alternatives

AspectPrisma (same DB)Hardcoded JSONHeadless CMS (Strapi)Revisium
PurposeTransactional dataStatic configContent managementReference data
Schema.prisma fileManual typesAdmin panelJSON Schema in Admin UI
MigrationsSQL filesN/APlugin-dependentJSON snapshot
Typed ClientPrismaClientManualSDK / RESTGenerated from OpenAPI
VersioningMigrations onlyGitNoneBranches, revisions, rollback
UIPrisma Studio (dev)NoneBuilt-inAdmin UI (production-ready)
Who editsDeveloperDeveloperContent teamContent team / AI agent
Content reviewNoPR reviewWorkflow pluginsBuilt-in draft → commit
RollbackManual SQLgit revertNoOne-click revert

Key Takeaways

  • Prisma for business domain, Revisium for reference data — clear separation of concerns
  • Content managers work autonomously through Admin UI — no PRs for content changes
  • Full type safety via OpenAPI code generation — same DX as Prisma
  • Git-like workflow for content: draft → review → commit → rollback
  • Same migration pattern as Prisma: save to file → commit to Git → apply on deploy