Getting Started

Scaffold with create-nextrush

Generate a runnable NextRush project with one command — pick a style, a middleware preset, and a runtime, and skip the manual setup from Installation.

Start · Scaffold

Wiring a project by hand teaches how the pieces fit — that's what Installation and the Task API tutorial are for. Once you've done that once, create-nextrush generates the same foundation in one command: tsconfig.json, scripts, middleware wiring, a sample route or controller, and a real unit test.

Functional, class-based, or full layout

Node, Bun, or Deno scripts

Middleware preset wired for you

Runnable + testable out of the box

~2 minNode 22+ to run CLIInteractive or flags

What you'll have

By the end, a new folder runs with pnpm dev (or your package manager's equivalent):

ProjectGeneratedpackage.json, tsconfig, src/, .gitignore
ServerRunningWelcome route + /health (or /api/health)
TestsPassingvitest run — at least one real unit test

Prerequisites

CLI runtime

Node.js 22+ — the scaffolder itself runs on Node

Package manager

pnpm, npm, yarn, or bun

When to scaffold

New project — not adding one package to an existing app

Prefer wiring every file yourself first? Start with Installation or the Task API tutorial, then come back here for your second project.

Run it

The scaffolder is the npm package create-nextrush. Your package manager can invoke it several ways:

$ pnpm create nextrush my-api

create nextrush vs. create-nextrush

pnpm create nextrush and npm create nextrush use a space — your package manager expands create <name> to the npm package create-<name>, so nextrush resolves to create-nextrush. Calling the package directly (npx, pnpm dlx, bunx) needs the hyphenated name: pnpm dlx create-nextrush, not pnpm dlx create nextrush.

my-api is the target directory — pass . to scaffold into the current folder, or omit it to be prompted. The flow is built on @clack/prompts.

How it works

Six prompts, every one with a default — press Enter to accept.

1

Project name

Where to create the project. Defaults to my-nextrush-app if you didn't pass a directory. The scaffolder derives package.json's name from this path — invalid characters become hyphens, so My API! becomes my-api.

2

Style

Three choices. Functional is the default — the smallest surface.

Functional (default)

Routes only, no decorators. Smallest surface — best for small APIs and microservices.

Class-based

Controllers, DI, and decorators. Routes register under /api. Best when you want structure from day one.

Full

Both styles side by side. A reference layout showing functional routes and class-based controllers together.

Full comparison with generated trees and code: Project styles.

3

Runtime

Node.js (default)

No adapter needed — nextrush re-exports @nextrush/adapter-node.

Bun

Adds @nextrush/adapter-bun and swaps the generated import and scripts.

Deno

Adds @nextrush/adapter-deno and swaps the generated import and scripts.

Edge and serverless aren't scaffolder targets

The runtime prompt only offers Node, Bun, and Deno. Edge and Serverless are paths you follow manually once a project exists.

4

Middleware preset

Minimal

No middleware — core only.

API (default)

cors, body-parser, helmet. Registered in production-safe order.

Full

API preset + rate-limit, compression, request-id.

The scaffolder writes the matching import lines and app.use(...) calls into src/index.ts.

5

Install dependencies?

Defaults to yes. Say no to scaffold files only and run pnpm install yourself afterward.

6

Initialize git?

Defaults to yesgit init plus an initial commit. Say no to skip.

Skip the prompts

Every prompt has a matching flag. A fully-specified command never pauses:

pnpm create nextrush my-api --style functional --runtime node --middleware api
pnpm create nextrush my-bun-api --style functional --runtime bun --middleware full
pnpm create nextrush my-deno-api --style class-based --runtime deno --middleware api

-y / --yes accepts every default without specifying each option:

pnpm create nextrush my-api --yes

Scaffold without install or git:

pnpm create nextrush my-api --yes --no-install --no-git
All CLI flags

create-nextrush flags

PropertyTypeDescription
--style, -s"functional" | "class-based" | "full"= "functional"Project style
--runtime, -r"node" | "bun" | "deno"= "node"Target runtime for the generated project
--middleware, -m"minimal" | "api" | "full"= "api"Middleware preset
--pm"npm" | "pnpm" | "yarn" | "bun"= auto-detected from how you invoked the scaffolder; "bun" if --runtime bunPackage manager for install and generated scripts
--install, -iboolean= trueInstall dependencies
--no-installbooleanSkip dependency installation
--gitboolean= trueInitialize a git repository
--no-gitbooleanSkip git initialization
-y, --yesboolean= falseAccept every default, skipping all interactive prompts
-v, --versionbooleanPrint the installed create-nextrush version
-h, --helpbooleanPrint flag usage

The project name comes from the directory you pass — invalid characters become hyphens, so My API! becomes my-api. Passing . names the project after the current folder.

Project styles

Every style shares tsconfig.json, package.json, README.md, .gitignore, and src/env.d.ts for editor hints — then adds its own source layout. Each ships at least one real vitest unit test, not a placeholder.

Best for: Small APIs, microservices, minimal surface area.

my-api
src
routes
__tests__
health-status.test.ts
health-status.ts
health.ts
index.ts
.gitignore
package.json
tsconfig.json

health-status.ts holds a pure function the route calls — testable without spinning up HTTP.

src/index.ts (generated, api middleware)
import { createApp, createRouter, listen } from 'nextrush';
import { cors } from '@nextrush/cors';
import { json } from '@nextrush/body-parser';
import { helmet } from '@nextrush/helmet';
import { healthRouter } from './routes/health.js';

const router = createRouter();
const app = createApp({ router });
const PORT = Number(process.env.PORT) || 8080;

// Middleware
app.use(cors());
app.use(helmet());
app.use(json());

// Routes
router.get('/', (ctx) => {
  ctx.json({ message: 'Welcome to NextRush!' });
});

app.route('/health', healthRouter);

await listen(app, PORT);

Health lives at GET /health. On Bun or Deno, listen comes from @nextrush/adapter-bun or @nextrush/adapter-deno instead. Deno reads Deno.env.get('PORT') for the port line.

Best for: DI, decorators, structured architecture from day one.

my-api
src
controllers
health.controller.ts
services
__tests__
app.service.test.ts
app.service.ts
index.ts
.gitignore
package.json
tsconfig.json

Controllers are auto-discovered — no manual mount list:

src/index.ts (generated excerpt)
import { createApp, createRouter, listen } from 'nextrush';
import { registerControllers } from 'nextrush/class';

const router = createRouter();
const app = createApp({ router });

await registerControllers(app, {
  root: CONTROLLERS_ROOT,
  include: CONTROLLERS_INCLUDE,
  prefix: '/api',
  strict: true,
});

await listen(app, PORT);
src/controllers/health.controller.ts (generated)
import { Controller, Get } from 'nextrush/class';
import { AppService } from '../services/app.service.js';

@Controller('/health')
export class HealthController {
  constructor(private readonly appService: AppService) {}

  @Get()
  check() {
    return this.appService.getHealth();
  }
}

Health lives at GET /api/health — the /api prefix comes from registerControllers, not the controller decorator alone.

Choosing class-based enables experimentalDecorators and emitDecoratorMetadata in tsconfig.json, and adds reflect-metadata plus @nextrush/class as explicit dependencies — same requirement as Installation's TypeScript step.

Best for: Both routing styles in one service. A reference layout.

my-api
src
controllers
hello.controller.ts
middleware
error-handler.ts
routes
health.ts
services
__tests__
hello.service.test.ts
hello.service.ts
index.ts
.gitignore
package.json
tsconfig.json

full combines a functional /health route, class-based /api/hello controllers, and error-handler.ts registered first in the middleware chain. The entry file uses serve() instead of listen() so startup can log the bound port.

Useful as a reference when you want both routing styles in one service — not the smallest starting point.

Generated scripts reference

package.json scripts differ by runtime. Every style also includes "test": "vitest run".

Runtimedevbuildstarttest
nodenextrush devnextrush buildnode dist/index.jsvitest run
bunbun nextrush devbun nextrush buildbun dist/index.jsvitest run
denodeno run … npm:nextrush devdeno run … npm:nextrush builddeno run --allow-net --allow-read --allow-env dist/index.jsvitest run

Deno's dev/build scripts route through nextrush dev and nextrush build (not a raw deno run on the entry file) so decorator metadata stays consistent with Node and Bun. The --allow-* flags are Deno's permission model — see Deno onboarding.

After scaffolding

cd my-api

pnpm dev     # hot-reload dev server
pnpm test    # run the generated unit test(s)
pnpm build   # production build
pnpm start   # run the production build

Verify the server (functional style, default middleware):

curl http://localhost:8080/
# → {"message":"Welcome to NextRush!"}

curl http://localhost:8080/health
# → {"status":"ok","timestamp":"…","uptime":…}

Class-based projects answer on /api/health instead of /health.

Code generators

Once the project exists, nextrush generate (alias nextrush g) adds controllers, services, middleware, guards, and routes — see the generators guide.

Common mistakes

Wrong package name with dlx/npx

Symptom:
Package not found when running pnpm dlx create nextrush
Cause:
Direct invocations need the hyphenated npm name, not the create-space form

✅ Fix: Use pnpm dlx create-nextrush or npx create-nextrush@latest

Looking for /health on a class-based project

Symptom:
404 on GET /health after scaffolding class-based or full
Cause:
Controllers register under the /api prefix

✅ Fix: Try GET /api/health — or scaffold functional if you want /health at the root

Git or install step failed

Symptom:
CLI prints failed - see the error above but files exist on disk
Cause:
Missing git on PATH, network blocked during install, or permission error

✅ Fix: Files are already written — retry git init or pnpm install manually

Scaffolding into a non-empty directory

Symptom:
CLI asks for confirmation before overwriting
Cause:
Target folder already has files

✅ Fix: Confirm only if you mean to merge, or pass a fresh directory name

What you learned

  • ✓ Invoke the scaffolder with your package manager — pnpm, bun, npm, or yarn
  • ✓ Three project styles: Functional (smallest), Class-based (structured), Full (both)
  • ✓ Runtime selection changes the adapter import and scripts — Node, Bun, or Deno
  • ✓ Middleware presets wire cors, body-parser, helmet (API) + rate-limit, compression, request-id (Full) in production-safe order
  • ✓ Every style ships at least one real vitest unit test, not a placeholder

Scaffold completeYour project is generated, dependencies installed, and a real test is passing. Run pnpm dev to start the server.

Next steps

Was this helpful?

On this page