Kanel

3 min read Original article ↗

Source of truth: PostgreSQL

Kanel is a code generation framework that transforms your PostgreSQL database schema into various output formats. Most commonly, it generates TypeScript types that look like this:

ts

// @generated
// This file is automatically generated by Kanel. Do not modify manually.

import { countryId, type CountryId } from './Country';
import { z } from 'zod';

/** Identifier type for city */
export type CityId = number & { __flavor?: 'CityId' };

/** Represents the table public.city */
export default interface City {
  /** Database type: pg_catalog.int4 */
  city_id: CityId;

  /** Database type: pg_catalog.varchar */
  city: string;

  /** Database type: pg_catalog.int2 */
  country_id: CountryId;

  /** Database type: pg_catalog.timestamp */
  last_update: Date;
}

/** Represents the initializer for the table public.city */
export interface CityInitializer {
  /**
   * Database type: pg_catalog.int4
   * Default value: nextval('city_city_id_seq'::regclass)
   */
  city_id?: CityId;

  /** Database type: pg_catalog.varchar */
  city: string;

  /** Database type: pg_catalog.int2 */
  country_id: CountryId;

  /**
   * Database type: pg_catalog.timestamp
   * Default value: now()
   */
  last_update?: Date;
}

/** Represents the mutator for the table public.city */
export interface CityMutator {
  /** Database type: pg_catalog.int4 */
  city_id?: CityId;

  /** Database type: pg_catalog.varchar */
  city?: string;

  /** Database type: pg_catalog.int2 */
  country_id?: CountryId;

  /** Database type: pg_catalog.timestamp */
  last_update?: Date;
}

export const cityId = z.number() as unknown as z.Schema<CityId>;

export const city = z.object({
  city_id: cityId,
  city: z.string(),
  country_id: countryId,
  last_update: z.date(),
}) as unknown as z.Schema<City>;

export const cityInitializer = z.object({
  city_id: cityId.optional(),
  city: z.string(),
  country_id: countryId,
  last_update: z.date().optional(),
}) as unknown as z.Schema<CityInitializer>;

export const cityMutator = z.object({
  city_id: cityId.optional(),
  city: z.string().optional(),
  country_id: countryId.optional(),
  last_update: z.date().optional(),
}) as unknown as z.Schema<CityMutator>;

How It Works

Kanel inspects a live PostgreSQL database and uses generators to produce code or documentation. Think of it as a reverse ORM that keeps your database as the source of truth.

The Generator Architecture

Kanel v4 is built around a pluggable generator system:

  • PgTsGenerator - Generates TypeScript types (the most common use case)
  • MarkdownGenerator - Generates human/LLM-friendly documentation
  • Custom generators - You can write generators for Python, Rust, GraphQL, or anything else

javascript

const { makePgTsGenerator, makeMarkdownGenerator } = require('kanel');

module.exports = {
  connection: /* ... */,
  generators: [
    makePgTsGenerator(),      // Generate TypeScript types
    makeMarkdownGenerator(),  // Generate documentation
  ],
};

Why Kanel?

  • Database as source of truth - Your schema drives your types, not the other way around
  • Type safety - Generate accurate TypeScript types including branded IDs, nullability, and relations
  • Extensible - Integrate with Kysely, Zod, Knex, and more through hooks
  • Flexible - Write custom generators for any language or format
  • Developer workflow - Check generated code into git and treat it as part of your codebase

Quick Start

bash

# Try it instantly
npx kanel -d postgresql://localhost:5432/mydb -o ./src/models

# Or install it
npm i -D kanel

See Getting Started for a complete guide.

Common Use Cases

TypeScript + Kysely

Generate TypeScript types with Kysely database interface:

javascript

const { makePgTsGenerator } = require('kanel');
const { makeKyselyHook } = require('kanel-kysely');

module.exports = {
  connection: /* ... */,
  generators: [
    makePgTsGenerator({
      preRenderHooks: [makeKyselyHook()],
    }),
  ],
};

TypeScript + Zod Schemas

Generate both TypeScript types and Zod validation schemas:

javascript

const { makePgTsGenerator } = require('kanel');
const { generateZodSchemas } = require('kanel-zod');

module.exports = {
  connection: /* ... */,
  generators: [
    makePgTsGenerator({
      preRenderHooks: [generateZodSchemas],
    }),
  ],
};

Documentation Only

Generate markdown documentation without TypeScript:

javascript

const { makeMarkdownGenerator } = require('kanel');

module.exports = {
  connection: /* ... */,
  generators: [makeMarkdownGenerator()],
};

See the examples directory for complete configurations.

Learn More

The idea was introduced in this blog post.


Copyright © 2018 Kristian Dupont, licensed under the MIT License

Cinnamon