Skip to Content

Getting started

Write a first route, add a service, and run it on Bun or Node.

Install

bun add @sukujs/core @sukujs/platform-bun zod

Any Standard Schema library works in place of Zod. Use @sukujs/platform-node when you deploy on Node. Packages are published under the @sukujs scope on npm; the current release is 0.1.0.

A first route

src/main.ts
import { Suku, route } from '@sukujs/core';
import { bunAdapter } from '@sukujs/platform-bun';
import { z } from 'zod';

const hello = route.get('/hello/:name', {
  query: z.object({ shout: z.coerce.boolean().default(false) }),
  handle: ({ params, query }) => {
    const text = `Hello, ${params.name}`;
    return { message: query.shout ? text.toUpperCase() : text };
  },
});

const app = await Suku.create({ routes: [hello], adapter: bunAdapter() });
const server = await app.listen(3000);
console.log(`listening on ${server.url}`);
bun run src/main.ts
curl 'localhost:3000/hello/ada?shout=true'
# {"message":"HELLO, ADA"}

This style needs no decorators and no compiler options.

Add a service and a routes class

Classes that receive services through their constructor need TypeScript's decorator metadata. UsersService below takes nothing in its constructor, so it needs no decorator at all.

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
src/users.ts
import { NotFoundException, Routes, feature, route, singleton } from '@sukujs/core';
import { z } from 'zod';

export class UsersService {
  private readonly users = new Map([[1, { id: 1, name: 'Ada Lovelace' }]]);

  find(id: number) {
    return this.users.get(id);
  }
}

@Routes('/users')
export class UsersRoutes {
  constructor(private readonly users: UsersService) {}

  findOne = route.get('/:id', {
    params: z.object({ id: z.coerce.number().int() }),
    handle: ({ params }) => {
      const user = this.users.find(params.id);
      if (!user) throw new NotFoundException(`User ${params.id} not found`);
      return user;
    },
  });
}

export const UsersFeature = feature('users', {
  routes: [UsersRoutes],
  services: [singleton(UsersService)],
});
src/main.ts
const app = await Suku.create({ features: [UsersFeature], routes: [hello], adapter: bunAdapter() });

Turn off type-only import rewriting

Lint rules that rewrite imports used only as types into import type erase the class that constructor injection needs. Disable Biome's style/useImportType and ESLint's consistent-type-imports. Suku reports the problem at startup as an UnresolvableParameterError naming the parameter.

Run it

Bun runs TypeScript and emits decorator metadata itself.

bun run src/main.ts

Next

On this page