Both, with Postgres
The live example is a server and a React app over thirteen collections: companies, tickets, appointments and an org chart in Postgres, files from a directory, flights, earthquakes and GitHub repositories from the net, the menu from a YAML file. Run it, then do the same with a collection of your own.
The example on your machine
Section titled “The example on your machine”You need Node 24 and Postgres on localhost with a role jsudc (password 123, CREATEDB);
backend/.env.example has the variables that point elsewhere.
-
Clone and install:
Terminal window git clone https://github.com/digital-cube/jsudc-example.git && cd jsudc-examplenvm use && npm ci -
The databases.
db:setupcreatesjsudcandjsudc_testand applies the migrations to both: the example’s eight tables and the UDC tables (the schema revisions of every Postgres collection).Terminal window npm run db:setup -
The rows.
seedwrites every Postgres collection’sschema.jsonas schema revision 1 and inserts its rows;--resetempties them first, back to the files.Terminal window npm run seed -- --reset -
Start both halves:
Terminal window npm run dev # server on :5100 (OpenAPI UI at /docs), React app on :5173Open
localhost:5173/udc/companies; every collection is in the sidebar.
Your own collection in Postgres
Section titled “Your own collection in Postgres”A collection directory without a source.yaml is a Postgres table named like the directory,
each row a jsonb document validated against the collection’s active schema revision. Take the
books directory of the server quick start, drop its source.yaml, and
keep schema.json, view.yaml and seed.json.
-
Install the server with its Postgres peers:
Terminal window npm install @jsudc/server @jsudc/core fastify zod drizzle-orm pg -
Create the tables and seed them, once:
seed.ts import { readFileSync } from "node:fs";import {connect,createCollectionTableSql,ensureUdcSchema,readCollectionAssets,seedCollection,} from "@jsudc/server";import { sql } from "drizzle-orm";const handle = connect(process.env.DATABASE_URL ?? "postgresql://localhost/app");await ensureUdcSchema(handle.db);for (const asset of readCollectionAssets("./collections")) {if (asset.source.kind !== "postgres_jsonb" || !asset.seedFile) continue;await handle.db.execute(sql.raw(createCollectionTableSql(asset.name)));const res = await seedCollection(handle.db, {table: asset.name,schema: asset.schemaJson,rows: JSON.parse(readFileSync(asset.seedFile, "utf8")),});console.log(`${res.table}: ${res.inserted} rows, schema revision 1 ${res.revision}`);}await handle.close();Terminal window DATABASE_URL=postgresql://localhost/books node seed.ts# books: 3 rows, schema revision 1 created -
Serve them. The only differences from the memory server are the connection and
ensureUdcSchema, which creates the UDC tables when they are missing:server.ts import {connect,createDispatcher,ensureUdcSchema,loadRegistry,makeSource,udcPlugin,} from "@jsudc/server";import Fastify from "fastify";const { db } = connect(process.env.DATABASE_URL ?? "postgresql://localhost/app");await ensureUdcSchema(db); // the UDC tables: schema revisions; or apply migrations/*.sql yourselfconst registry = loadRegistry("./collections", (asset, registry) =>makeSource(asset, { db, registry, env: process.env }),);const app = Fastify({ logger: true });const dispatcher = createDispatcher(registry, { log: app.log });await app.register(udcPlugin, { dispatcher, registry, swagger: false });await app.listen({ port: 5100 });Terminal window DATABASE_URL=postgresql://localhost/books node server.ts
The React data view draws it unchanged. Now the schema editor writes: a
column added in the browser is a new schema revision in udc_schema_revisions, and a field
marked index: true gets its expression index.