メインコンテンツまでスキップ

Getting Started with Node.js

This guide walks you through building a Zelt application on Node.js from scratch.

Prerequisites

  • Node.js v20 or higher (or Bun v1.0 or higher)
  • A package manager: pnpm (recommended), npm, or bun

Installation

pnpm add @zeltjs/core @zeltjs/adapter-node

Project Structure

my-app/
├── src/
│   ├── entry/
│   │   ├── controllers/    # HTTP endpoints
│   │   └── commands/       # CLI commands
│   ├── services/           # Business logic
│   ├── configs/            # Configuration classes
│   ├── app.ts              # Application definition
│   ├── cli.ts              # CLI entry point
│   └── main.ts             # HTTP server entry point
├── package.json
└── tsconfig.json
DirectoryPurpose
entry/External entry points (HTTP, CLI)
services/Business logic, injected via DI
configs/Environment variables and settings

Hello World

Step 1: Create the Controller

Controllers handle incoming HTTP requests and return responses. Each controller is a class decorated with @Controller that defines a route prefix.

Create src/entry/controllers/hello.controller.ts:

@Controller('/hello')
export class HelloController {
  @Get('/:name')
  greet(name = pathParam('name')) {
    return { message: `Hello, ${name}!` };
  }
}
  • @Controller('/hello') — Sets the base path for all routes in this controller
  • @Get('/:name') — Handles GET requests to /hello/:name
  • pathParam('name') — Extracts the name parameter from the URL path

Step 2: Create the Application

Create src/app.ts to wire up your controllers and prepare for the Node.js runtime:

export const app = createApp([http({
    controllers: [HelloController],
  })]);

export default await onNode(app);

The onNode() function prepares your app for the Node.js runtime, returning a NodeApp with listen(), get(), and args properties.

Step 3: Start the Server

Create src/main.ts to start the server:

const server = await nodeApp.listen({ port: 3000 });
console.log(`Server running at http://localhost:${server.address.port}`);

Step 4: Configure TypeScript

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Step 5: Run the Application

npx tsx src/main.ts

Visit http://localhost:3000/hello/world to see:

{ "message": "Hello, world!" }

Adding Services

Services contain business logic and can be injected into controllers. Use @Injectable to mark a class as a service.

Create src/services/greeting.service.ts:

@Injectable()
export class GreetingService {
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

Update your controller to use the service:

@Controller('/hello')
export class HelloController {
  constructor(private greetingService = inject(GreetingService)) {}

  @Get('/:name')
  greet(name = pathParam('name')) {
    return { message: this.greetingService.greet(name) };
  }
}

Configuration

Zelt provides configuration classes for managing environment variables.

Using Environment Variables

@Controller('/config')
export class ConfigController {
  constructor(private env = inject(Env)) {}

  @Get('/api-host')
  getApiHost() {
    return { apiHost: this.env.getString('API_HOST', 'localhost') };
  }
}

onNode() automatically registers the env adaptor, so inject(Env) reads from process.env without any extra configuration. For .env files, import dotenv/config at your entry point.

What's Next?

Now that you have a basic application running, explore more features: