DEV Community

Cover image for The KickJS CLI: Scaffolding Production-Ready Node.js Backends in Seconds
Orinda Felix Ochieng
Orinda Felix Ochieng

Posted on

The KickJS CLI: Scaffolding Production-Ready Node.js Backends in Seconds

A deep dive into the code generation tool that makes KickJS feel like Rails — except it's TypeScript, it's Express 5, and the scaffolds are actually good.


Why a CLI Matters

Every backend framework eventually confronts the same problem: boilerplate. You know the shape of a new feature before you start typing — a controller, a service, a repository, DTOs, a module registration, maybe a test file. You've written it a hundred times. You're going to write it a hundred more. The code isn't hard; it's just tedious, and the tedium invites inconsistency.

The answer is code generation. Rails has rails g. Angular has ng g. NestJS has nest g. KickJS has kick g. But where NestJS generators feel like templates-with-string-substitution, KickJS generators are built to produce the same well-structured code your team would write by hand after a code review — wired up correctly, typed end-to-end, and ready to run without edits.

This article is the guided tour of the kick CLI. By the end, you'll know how to:

  • Scaffold a new project in one command
  • Generate full DDD modules with 16 files in about two seconds
  • Use the field-driven scaffold generator to create CRUD from a type description
  • Add KickJS packages with their peer dependencies resolved automatically
  • Define custom commands in kick.config.ts so your project-specific workflows become first-class CLI citizens
  • Inspect a running application's routes, middleware, and DI container
  • Generate fully-typed route handlers via the typegen system

Let's start from zero.


Installing the CLI

There are three ways to run kick, and each has its place.

1. Global install (recommended for daily use)

pnpm add -g @forinda/kickjs-cli
# or: npm install -g @forinda/kickjs-cli
# or: yarn global add @forinda/kickjs-cli

kick --version
Enter fullscreen mode Exit fullscreen mode

With a global install, kick is on your $PATH and you can run it from anywhere. It'll pick up the project's local kick.config.ts automatically when you run it inside a project directory.

2. npx / pnpm dlx / yarn dlx (no install)

npx @forinda/kickjs-cli new my-api
# or: pnpm dlx @forinda/kickjs-cli new my-api
# or: yarn dlx @forinda/kickjs-cli new my-api
Enter fullscreen mode Exit fullscreen mode

Good for one-off project creation or CI pipelines where you don't want a global install.

3. Project-local (inside the monorepo or a single project)

Once a KickJS project is created, @forinda/kickjs-cli is already in devDependencies. You can run it via:

pnpm kick dev
pnpm kick g module user
Enter fullscreen mode Exit fullscreen mode

This is what most of the examples in this article use. It ensures you're running the exact CLI version pinned in the project's lockfile, which avoids the "works on my machine" problem when the CLI is upgraded.


Creating a Project: kick new

Here's the single command that creates a complete, runnable backend:

kick new my-api
Enter fullscreen mode Exit fullscreen mode

Run that without any flags and the CLI walks you through five interactive prompts:

  1. Project template — REST, GraphQL, DDD, CQRS, or Minimal
  2. Package manager — pnpm, npm, or yarn
  3. Default repository — Prisma, Drizzle, In-Memory, or a custom ORM
  4. Git init — initialize a git repository and make the first commit
  5. Install deps — run the selected package manager immediately

If you know what you want, skip the prompts entirely:

kick new my-api \
  --template rest \
  --repo drizzle \
  --pm pnpm \
  --git \
  --install
Enter fullscreen mode Exit fullscreen mode

For CI pipelines or shell scripts where no TTY is available, this non-interactive form is essential. Every prompt has a corresponding flag, and the CLI errors out clearly if you leave a required field blank.

The Five Templates

The --template flag picks the shape of the generated project:

Template What You Get Packages Pre-installed
rest (default) Express + Swagger + DevTools kickjs, kickjs-vite, kickjs-swagger
graphql GraphQLAdapter + GraphiQL kickjs, kickjs-vite, kickjs-graphql
ddd Full DDD layering for each module kickjs, kickjs-vite, kickjs-swagger
cqrs Commands, queries, events, WS, queue, OTel kickjs, kickjs-vite, plus 4 more
minimal Bare Express — no adapters kickjs, kickjs-vite

The rest and ddd templates are 90% of what people want. rest gives you a flat structure where each module has its controller and service at the top level, which is perfect for small to medium APIs. ddd produces a layered structure with presentation/, application/, domain/, and infrastructure/ folders — the right choice when your domain logic deserves to be isolated from your HTTP plumbing.

Safety Nets

The scaffolder is careful about destructive operations. If the target directory already exists and isn't empty, it shows up to five entries from the directory and asks whether to clear them. Pass --force to skip the confirmation.

And — a small but meaningful fix we added recently — the CLI installs dependencies before running git init, so your lockfile and any files produced by kick typegen end up in the initial commit. You won't see a dangling "chore: add lockfile" commit as the second entry in your history.


The Dev Loop: kick dev, kick build, kick start

Once a project exists, three commands handle the entire lifecycle.

kick dev

kick dev
kick dev -e src/main.ts     # custom entry file
kick dev -p 4000            # custom port (overrides Vite default of 5173)
Enter fullscreen mode Exit fullscreen mode

This starts the Vite development server in SSR mode. It's the secret sauce behind the framework's "instant reload without losing state" pitch. Edit a controller, save the file, and within 50 milliseconds the Express handler is swapped out — database connections, WebSocket state, and in-memory caches all survive intact. You can keep a Postgres connection pool open across hundreds of reloads.

The server listens on port 5173 by default (Vite's default). Override with -p or by setting PORT=3000 in your .env file.

kick dev:debug

Same as kick dev, but attaches a Node.js debugger. Point Chrome DevTools, VS Code, or your IDE's inspector at the debug port and you can set breakpoints that survive across HMR reloads. This is the one I use when I'm tracking down a bug that only appears after three requests.

kick build

kick build
Enter fullscreen mode Exit fullscreen mode

Runs vite build with the framework's production defaults — ESM output, node20 target, proper externalization of native modules. The output lands in dist/. If your kick.config.ts lists copyDirs (for views, email templates, static assets), those get copied over as part of the build.

kick start

kick start
kick start -e dist/main.js
kick start -p 8080
Enter fullscreen mode Exit fullscreen mode

Runs the built output with NODE_ENV=production. This is what you put in your Dockerfile or process manager. It doesn't use Vite at all — it's just node dist/index.js with some convenience wrappers for port and entry resolution.


Code Generation: kick g

This is the part you'll use most. The generate command (aliased to g) scaffolds code according to your project's pattern and conventions.

Run kick g --list to see every available generator in your terminal. Here's the shortlist:

Command What It Generates
kick g module <name> Full feature module (controller, DTOs, use-cases, repo)
kick g scaffold <name> <fields...> Same as module, but with concrete fields
kick g controller <name> Standalone @Controller class
kick g service <name> Standalone @Service class
kick g middleware <name> Express middleware function
kick g guard <name> Route guard for auth/roles
kick g dto <name> Zod DTO schema
kick g adapter <name> AppAdapter with lifecycle hooks
kick g test <name> Vitest test scaffold
kick g resolver <name> GraphQL resolver
kick g job <name> Queue job processor
kick g config Generate kick.config.ts

Let's look at the important ones.

kick g module — Your New Best Friend

kick g module user
Enter fullscreen mode Exit fullscreen mode

That one command creates the entire feature structure. With the default DDD pattern and the in-memory repo, you get 16 files organized into layers:

src/modules/users/
├── index.ts                                          # Module wiring
├── constants.ts                                      # Query config + DI tokens
├── presentation/
│   └── user.controller.ts                            # Full CRUD controller
├── application/
│   ├── dtos/
│   │   ├── create-user.dto.ts                        # Zod schema
│   │   ├── update-user.dto.ts
│   │   └── user-response.dto.ts
│   └── use-cases/
│       ├── create-user.use-case.ts
│       ├── get-user.use-case.ts
│       ├── list-users.use-case.ts
│       ├── update-user.use-case.ts
│       └── delete-user.use-case.ts
├── domain/
│   ├── entities/user.entity.ts
│   ├── value-objects/user-id.vo.ts
│   ├── repositories/user.repository.ts              # Interface + createToken
│   └── services/user-domain.service.ts
└── infrastructure/
    └── repositories/in-memory-user.repository.ts    # Working implementation
Enter fullscreen mode Exit fullscreen mode

The generator also updates src/modules/index.ts to register the new module in the AppModuleClass[] array. No manual imports, no "forgot to register the module" bugs.

Pluralization is automatic. kick g module user produces src/modules/users/ (plural folder, plural route /users) but classes stay singular: UserController, UserService, UserEntity. The framework uses the battle-tested pluralize npm package, so irregular plurals work correctly: kick g module person produces src/modules/people/, kick g module status produces src/modules/statuses/, and kick g module category produces src/modules/categories/. Disable pluralization with --no-pluralize or set modules.pluralize: false in kick.config.ts.

Generate Multiple Modules at Once

kick g module user task project
Enter fullscreen mode Exit fullscreen mode

Takes a list and creates all three modules in sequence. Each one is independently registered. This is nice when you're laying down the structure of a new feature area and already know the three modules you'll need.

Pattern Overrides

The default pattern comes from kick.config.ts, but you can override per-invocation:

kick g module user --pattern rest      # flat structure
kick g module user --pattern minimal   # just index.ts + controller
kick g module user --pattern cqrs      # commands, queries, events
Enter fullscreen mode Exit fullscreen mode

The REST pattern is particularly nice for smaller modules — you get the module, controller, service, repository interface, in-memory implementation, DTOs, and tests, all at the top level of the module folder without the layered directory nesting.

kick g scaffold — CRUD From a Field Description

This is the generator that feels like magic. Instead of editing generated DTOs after the fact, you describe the fields up front and the scaffold generator produces working code with concrete types:

kick g scaffold post \
  title:string \
  body:text \
  published:boolean:optional \
  publishedAt:date:optional
Enter fullscreen mode Exit fullscreen mode

The field syntax is name:type or name:type:optional. Here are the supported types:

Type TypeScript Zod Example
string string z.string() title:string
text string z.string() body:text
number number z.number() price:number
int number z.number().int() age:int
float number z.number() rating:float
boolean boolean z.boolean() active:boolean
date string z.string().datetime() createdAt:date
email string z.string().email() email:email
url string z.string().url() website:url
uuid string z.string().uuid() externalId:uuid
json any z.any() metadata:json
enum:a,b,c `'a' \ 'b' \ 'c'`

And yes, you can combine them. A realistic scaffold command for a task management module might look like:

kick g scaffold task \
  title:string \
  description:text:optional \
  status:enum:todo,in_progress,done \
  priority:enum:low,medium,high \
  dueDate:date:optional \
  assigneeId:uuid:optional
Enter fullscreen mode Exit fullscreen mode

Run that and you get DTOs, entities, a query config that knows about every field, a controller with five typed routes, five use-cases, and a working in-memory repository. The generated createTaskSchema ends up looking like this:

import { z } from 'zod'

export const createTaskSchema = z.object({
  title: z.string(),
  description: z.string().optional(),
  status: z.enum(['todo', 'in_progress', 'done']),
  priority: z.enum(['low', 'medium', 'high']),
  dueDate: z.string().datetime().optional(),
  assigneeId: z.string().uuid().optional(),
})

export type CreateTaskDTO = z.infer<typeof createTaskSchema>
Enter fullscreen mode Exit fullscreen mode

No hand-written type annotations, no "I'll fix this later" TODO comments, just running code that matches the Zod contract.

About That :optional Suffix

You might have expected the optional marker to be ?, like in TypeScript: description?:text. That was the original design, and it still works — but only if you quote the argument, because ? is a shell glob character in bash and zsh. Without quotes, description?:text gets expanded to match any filename in the current directory starting with description and having one character before :text, which is almost never what you want and produces cryptic "no matches found" errors.

The :optional suffix is the shell-safe alternative. It works in any shell, without quoting, and it's unambiguous. You can still use ? if you quote the field ("description:text?" or "description?:text" both work), but :optional is what the docs recommend and what I'd use in scripts.

kick g Inside a Module: The -m Flag

Sometimes you don't want a whole new module — you want to add a single file to an existing one. The -m flag scopes standalone generators to a module:

kick g controller auth -m users       # → src/modules/users/presentation/auth.controller.ts
kick g service payment -m orders      # → src/modules/orders/domain/services/payment.service.ts
kick g dto create-user -m users       # → src/modules/users/application/dtos/create-user.dto.ts
kick g guard admin -m users           # → src/modules/users/presentation/guards/admin.guard.ts
kick g middleware cache -m products   # → src/modules/products/middleware/cache.middleware.ts
Enter fullscreen mode Exit fullscreen mode

The CLI uses the project pattern (from kick.config.ts) to decide which subfolder inside the module to drop the file into. For the DDD pattern, controllers go to presentation/, services go to domain/services/, DTOs go to application/dtos/, and so on. For the flat REST pattern, everything goes to the module root.

If -m isn't given, standalone generators create files in app-level default directories: src/controllers/, src/services/, src/middleware/, etc.

Override the destination with -o, --out <dir>:

kick g controller health -o src/system
# → src/system/health.controller.ts
Enter fullscreen mode Exit fullscreen mode

kick g With Dry Run

Before committing to a generator, you can preview the files it would create:

kick g module user --dry-run
Enter fullscreen mode Exit fullscreen mode

Prints the list of files that would be generated but doesn't write anything. Useful when you're experimenting with generator flags and don't want to clean up afterwards.

kick rm module

The inverse of kick g module:

kick rm module user
kick rm module user task project   # delete multiple
kick rm module user --force        # skip confirmation
Enter fullscreen mode Exit fullscreen mode

Deletes the module directory and removes its entry from src/modules/index.ts. Good for when you generated a module with the wrong name or pattern and want to start over.


Package Management: kick list, kick add

KickJS is a monorepo with 18+ published packages. Finding them and adding them with the right peer dependencies used to mean reading the docs. Now it's two commands.

kick list

kick list
# or: kick ls
Enter fullscreen mode Exit fullscreen mode

Prints every available package with its description and peer dependencies:

  Available KickJS packages:

    kickjs           Unified framework: DI, decorators, routing, middleware (+ express)
    vite             Vite plugin: dev server, HMR, module discovery (+ vite)
    swagger          OpenAPI spec + Swagger UI + ReDoc
    graphql          GraphQL resolvers + GraphiQL (+ graphql)
    drizzle          Drizzle ORM adapter + query builder (+ drizzle-orm)
    prisma           Prisma adapter + query builder (+ @prisma/client)
    ws               WebSocket with @WsController decorators (+ socket.io)
    otel             OpenTelemetry tracing + metrics (+ @opentelemetry/api)
    devtools         Development dashboard — routes, DI, metrics, health
    auth             Authentication — JWT, API key, and custom strategies (+ jsonwebtoken)
    mailer           Email — SMTP, Resend, SES, or custom provider (+ nodemailer)
    cron             Cron job scheduling (production-grade with croner) (+ croner)
    queue            Queue adapter (BullMQ/RabbitMQ/Kafka)
    queue:bullmq     Queue with BullMQ + Redis (+ bullmq, ioredis)
    queue:rabbitmq   Queue with RabbitMQ (+ amqplib)
    queue:kafka      Queue with Kafka (+ kafkajs)
    multi-tenant     Tenant resolution middleware
    notifications    Multi-channel notifications — email, Slack, Discord, webhook
    testing          Test utilities and TestModule builder
Enter fullscreen mode Exit fullscreen mode

kick add

kick add graphql           # adds @forinda/kickjs-graphql + graphql
kick add drizzle otel      # installs multiple packages at once
kick add queue:bullmq      # installs the queue package + bullmq + ioredis
kick add --list            # same as `kick list`
Enter fullscreen mode Exit fullscreen mode

The important detail: kick add resolves peer dependencies for you. When you run kick add graphql, the CLI installs both @forinda/kickjs-graphql and graphql itself, because the GraphQL package has graphql as a peer. You don't have to read package.json or guess which peer version range to pin.

The package manager is auto-detected from your lockfile — if you have a pnpm-lock.yaml, it uses pnpm; if you have a package-lock.json, npm; and so on. Override with --pm <manager> if needed. Use -D, --dev to install as a dev dependency.

The queue: packages are a special case: they're meta-packages that install the queue abstraction plus a specific backend. queue:bullmq installs @forinda/kickjs-queue, bullmq, and ioredis in one go. That's the pattern to follow if you want "the queue system with a working backend" without thinking about it.


Environment Diagnostics: kick info

kick info
Enter fullscreen mode Exit fullscreen mode

Prints your system information and the versions of KickJS packages in the current project:

  KickJS CLI

  System:
    OS:       linux 6.x.x (x64)
    Node:     v20.x.x

  Packages:
    @forinda/kickjs           2.2.4
    @forinda/kickjs-cli       2.2.4
    @forinda/kickjs-swagger   2.2.4
Enter fullscreen mode Exit fullscreen mode

Paste the output into a GitHub issue when reporting a bug. It's the framework's equivalent of npx envinfo.


Inspecting a Running App: kick inspect

This one's more niche but genuinely useful when debugging. Start your dev server in one terminal, then in another terminal:

kick inspect
kick inspect --port 4000
kick inspect --watch      # re-inspect on every HMR reload
kick inspect --json       # raw JSON output for scripting
Enter fullscreen mode Exit fullscreen mode

You get a live snapshot of the running application:

  KickJS Inspector — http://localhost:5173

  Routes (12):
    GET     /health
    GET     /api/v1/users
    POST    /api/v1/users
    GET     /api/v1/users/:id
    PUT     /api/v1/users/:id
    DELETE  /api/v1/users/:id
    GET     /api/v1/posts
    POST    /api/v1/posts
    GET     /graphql          [GraphQLAdapter]
    WS      /chat             [WsAdapter]

  Adapters (3):
    GraphQLAdapter     /graphql
    WsAdapter          /chat
    DevToolsAdapter    /_debug

  Middleware (5):
    cors, helmet, csrf, rateLimit, session

  DI Container:
    Services:    8
    Controllers: 4
    Resolvers:   2
Enter fullscreen mode Exit fullscreen mode

This is faster than scrolling through startup logs and works in production too — you can run kick inspect --json > routes.json against a live app and commit the output as documentation.

The --watch flag is great during development. Keep the inspector in a split terminal pane and watch routes appear as you create controllers.


Custom Commands in kick.config.ts

Here's the feature that elevates the CLI from a scaffolding tool to a project runner: custom commands defined per-project. Open kick.config.ts at your project root:

import { defineConfig } from '@forinda/kickjs-cli'

export default defineConfig({
  pattern: 'rest',
  modules: {
    dir: 'src/modules',
    repo: 'drizzle',
    pluralize: true,
  },
  commands: [
    {
      name: 'db:migrate',
      description: 'Run Drizzle migrations against the dev database',
      steps: 'npx drizzle-kit migrate',
    },
    {
      name: 'db:seed',
      description: 'Seed the dev database with fixtures',
      steps: 'tsx scripts/seed.ts',
    },
    {
      name: 'db:reset',
      description: 'Drop, recreate, and reseed the dev database',
      steps: [
        'npx drizzle-kit drop',
        'npx drizzle-kit migrate',
        'tsx scripts/seed.ts',
      ],
      aliases: ['db:nuke'],
    },
  ],
})
Enter fullscreen mode Exit fullscreen mode

Once defined, these show up as first-class commands alongside the built-in ones:

kick db:migrate
kick db:seed
kick db:reset     # runs the three steps sequentially
kick db:nuke      # alias for db:reset
Enter fullscreen mode Exit fullscreen mode

Each command can run a single shell command or an array of commands that execute sequentially. The aliases field lets you define shorthand names.

The nice part is that the commands show up in kick --help, so a new developer on the team can run kick --help and immediately see every project-specific workflow — no separate README section to maintain, no Makefile to hunt through.

kick g config

If your project didn't have a kick.config.ts (older projects or minimal scaffolds), generate one with:

kick g config
kick g config --force                # overwrite existing
kick g config --modules-dir src/mods # custom modules path
kick g config --repo drizzle         # default repo
Enter fullscreen mode Exit fullscreen mode

Typed Routes: kick typegen

This is the feature that makes KickJS handlers feel different from every other TypeScript backend I've worked with.

kick typegen
Enter fullscreen mode Exit fullscreen mode

The typegen command scans your codebase for @Controller classes and their route decorators, reads the Zod schemas attached via @Post('/', { body: schema }), and produces a global KickRoutes namespace with one interface per controller. The result lands in .kickjs/types/routes.ts, and once it's there, your route handlers can reference the types directly:

import { Controller, Post, type Ctx } from '@forinda/kickjs'
import { z } from 'zod'

const createUserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
})

@Controller('/users')
export class UserController {
  @Post('/', { body: createUserSchema, name: 'CreateUser' })
  create(ctx: Ctx<KickRoutes.UserController['create']>) {
    // ctx.body.email is inferred as string — from the Zod schema above
    // ctx.params is typed as {} because there are no route params
    // ctx.query is typed as whatever the query parser produces
    ctx.created({ id: '1', ...ctx.body })
  }
}
Enter fullscreen mode Exit fullscreen mode

No manual z.infer<typeof createUserSchema>, no duplicated types between the controller and the service, no drift between what the decorator enforces at runtime and what TypeScript knows at compile time.

Typegen runs automatically on kick dev startup, and it re-runs every time you save a file that includes a controller. You don't run it manually except in three situations:

  1. CI builds — before tsc --noEmit so the types are present when the typechecker runs
  2. Fresh clones — so the first git clone + pnpm install + tsc sequence works
  3. Manual regeneration — when you've changed something major and want to force a rebuild

Similarly, the typegen system produces a KickEnv interface from your src/config/index.ts schema. Once it runs, @Value('DATABASE_URL') becomes type-safe — TypeScript knows the return type is string, and autocompletes the available env keys. Add a new env variable to the schema, save the file, and the new key is immediately typed everywhere.


The One-Liner Shortcuts

Because I use these dozens of times per day, here are the incantations I have muscle memory for:

# Create a new project, fully non-interactive
kick new my-api -t rest -r drizzle --pm pnpm --git --install

# Scaffold a CRUD module with fields
kick g scaffold product \
  name:string price:number stock:int:optional \
  status:enum:draft,active,archived

# Start dev with a custom port
kick dev -p 4000

# Add GraphQL + WebSockets in one shot
kick add graphql ws

# Inspect the running app
kick inspect --watch

# Run a custom db:reset command
kick db:reset
Enter fullscreen mode Exit fullscreen mode

None of these take more than a few seconds to type. All of them replace what would have been minutes of boilerplate.


What Makes a Good Generator

I want to end on the design philosophy, because I think it's what separates good code generators from bad ones, and it's worth understanding before you reach for any CLI tool.

A good generator produces code that you would actually write yourself. When I look at a file the scaffold created, I shouldn't cringe, and I shouldn't need to immediately reorganize the imports or rename the variables. The generator should have made the same choices a senior engineer on your team would make — conventional naming, idiomatic TypeScript, reasonable structure, no magic.

A good generator stays out of the way. Once a file is generated, the generator has no further opinion about it. You can edit the file freely, rename things, restructure it, and the generator won't complain the next time you run it against the same module name (you get an overwrite prompt, which is the correct behavior).

A good generator composes. Running kick g scaffold task doesn't prevent you from then running kick g guard admin -m tasks to add a guard. The generators know about the project pattern, so they cooperate on folder structure. You can iterate your way to a finished feature.

A good generator gives you an escape hatch. Every file the scaffold produces is editable, and you're never locked into the template. The scaffold is a starting point, not a cage. Swap the in-memory repository for a Drizzle one by replacing one file. Rewrite the controller by hand. The rest of the framework doesn't know or care.

The KickJS CLI tries to embody all four of these principles. It's not the most featureful CLI in the ecosystem, and it's not trying to be — but the code it produces is the code I want to see when I review a PR, and that's the bar I hold it to.


Closing Thoughts

If there's one thing I hope you take away from this article, it's this: the boring parts of backend development should be automated. The scaffolding, the wiring, the "create a file, add imports, register in the module" dance — that's not where you add value. The value is in the domain logic, the edge cases, the business rules, the test coverage. A good CLI frees you up to spend time on those things instead of typing the thousandth copy of an Express router.

KickJS isn't trying to hide what's underneath — it's Express 5, it's TypeScript, it's decorators, it's Zod. The CLI doesn't abstract those away; it just makes sure you spend less time typing the glue that holds them together.

Try it out:

npx @forinda/kickjs-cli new my-api
cd my-api
kick dev
Enter fullscreen mode Exit fullscreen mode

Two commands and a cd. That's the pitch.

If you want to go deeper, the full docs cover every flag and every generator. The source is on GitHub — contributions welcome, especially new generator templates. And if this article saved you some typing, a star on the repo is the closest thing to a thank-you I can receive.

Happy scaffolding.


KickJS is an open-source Node.js framework built on Express 5 and TypeScript. The CLI is @forinda/kickjs-cli. Feedback and bug reports go to GitHub issues.

Top comments (0)