Skip to main content

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
  • A package manager: pnpm (recommended), npm, or bun

Installation

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

Project Structure

my-app/
├── src/
│ ├── app.ts
│ ├── main.ts
│ └── controllers/
│ └── hello.controller.ts
├── package.json
└── tsconfig.json

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/controllers/hello.controller.ts:

import { Controller, Get, pathParam } from '@zeltjs/core';

@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:

import { createHttpApp } from '@zeltjs/core';
import { HelloController } from './controllers/hello.controller';

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

Step 3: Start the Server

Create src/main.ts for the Node.js entry point:

import { serve } from '@zeltjs/adapter-node';
import { app } from './app';

serve(app, { port: 3000 }, (info) => {
console.log(`Server running at http://localhost:${info.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:

import { Injectable } from '@zeltjs/core';

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

Update your controller to use the service:

import { Controller, Get, pathParam, inject } from '@zeltjs/core';
import { GreetingService } from '../services/greeting.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

import { Controller, Get, inject } from '@zeltjs/core';
import { EnvService } from '@zeltjs/core';

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

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

Register the config in your app:

import { createHttpApp, EnvConfig } from '@zeltjs/core';

export const app = createHttpApp({
controllers: [ConfigController],
configs: [EnvConfig],
});

Node.js-Specific Configs

The @zeltjs/adapter-node package provides additional configuration options:

import { ProcessEnvConfig, DotEnvConfig } from '@zeltjs/adapter-node';

// ProcessEnvConfig: Reads from process.env (default behavior)
// DotEnvConfig: Reads from .env file

What's Next?

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