Getting Started with Node.js
This guide walks you through building a Zelt application on Node.js from scratch.
Prerequisites
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/:namepathParam('name')— Extracts thenameparameter 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:
- Controllers — Route handling and HTTP methods
- Services — Business logic and dependency injection
- Validation — Request body validation with Valibot
- Middleware — Request/response interceptors
- Configuration — Advanced configuration patterns