Getting Started with Gati
Welcome to Gati - a next-generation TypeScript/Node.js framework for building cloud-native, versioned APIs with automatic scaling, deployment, and SDK generation.
Table of Contents
- Prerequisites
- Installation
- Quick Start
- Project Structure
- Basic Concepts
- Your First API Endpoint
- Running Your Application
- Next Steps
- Troubleshooting
Prerequisites
Before you begin, ensure you have the following installed:
- Node.js >= 18.0.0
- pnpm >= 8.0.0 (recommended) or npm >= 8.0.0
Check your versions:
node --version # Should be >= 18.0.0
pnpm --version # Should be >= 8.0.0If you don't have pnpm installed:
npm install -g pnpmInstallation
Create a New Project with GatiC
GatiC (Gati Command) is the official project scaffolding tool. Use it via npx to create new Gati applications:
npx gatic create my-app
cd my-appWhat is GatiC?
gatic- The project creation command (wrapper around@gati-framework/cli)- Used via
npx gatic create- no global installation needed - Always uses the latest published version
- Creates a complete project with all dependencies
What is the difference between gatic and gati?
gatic- Global command for creating new projects (npx gatic create)gati- Local command installed in your project for development and deployment- Use
gati devto start development server - Use
gati buildto build for production - Use
gati deployto deploy to Kubernetes
- Use
Option 2: Clone the Hello World Example
git clone https://github.com/krishnapaul242/gati.git
cd gati/examples/hello-world
pnpm installQuick Start
Let's build your first Gati application in under 5 minutes!
Step 1: Create a New Project
npx gatic create my-first-app
cd my-first-appThe scaffolder will prompt you for:
- Project description - Brief description of your app
- Author - Your name
- Template - Choose between Default (with examples) or Minimal
Step 2: Install Dependencies
Dependencies are automatically installed during creation. If you need to reinstall:
pnpm installStep 3: Understand the Project Structure
my-first-app/
├── src/
│ ├── index.ts # Application entry point (NEW in v2.0.0)
│ ├── handlers/ # Request handlers
│ │ ├── hello.ts # Example handler
│ │ └── health.ts # Health check endpoint (NEW)
│ └── modules/ # Reusable modules (optional)
├── deploy/
│ └── kubernetes/ # Kubernetes manifests (NEW)
│ ├── deployment.yaml
│ └── service.yaml
├── tests/
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
├── gati.config.ts # Application configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── Dockerfile # Production Docker image (NEW)
├── docker-compose.yml # Local Docker setup (NEW)
├── .env.example # Environment variables template
├── .gitignore
└── README.md # Project documentationWhat's new in Runtime v2.0.0:
- ✅
src/index.ts- Explicit application entry point usingcreateApp()andloadHandlers() - ✅
src/handlers/health.ts- Production-ready health check endpoint - ✅ Kubernetes manifests for deployment
- ✅ Docker support out of the box
- ✅ Comprehensive README with deployment instructions
Step 4: Examine Your First Handler
Open src/handlers/hello.ts:
/**
* @handler GET /hello
* @description Simple hello world handler
*/
import type { Handler } from '@gati-framework/runtime';
export const handler: Handler = (req, res) => {
const name = req.query.name || 'World';
res.json({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
});
};This simple handler demonstrates the core handler signature:
req- HTTP request object (Express.js compatible)res- HTTP response object (Express.js compatible)- Access query parameters via
req.query - Send JSON responses via
res.json()
Runtime v2.0.0 Changes:
- Handlers now use standard Express.js
reqandresobjects - Global context (
gctx) and local context (lctx) are available viareq.gatiContext - Simpler API for common use cases
Step 5: Understand the Application Entry Point
Open src/index.ts:
import { createApp, loadHandlers } from '@gati-framework/runtime';
async function main() {
const app = createApp({
port: Number(process.env['PORT']) || 3000,
host: process.env['HOST'] || '0.0.0.0'
});
await loadHandlers(app, './src/handlers', {
basePath: '/api',
verbose: true
});
await app.listen();
console.log(`Server running on ${app.getConfig().host}:${app.getConfig().port}`);
// Graceful shutdown
const shutdown = async (signal: string) => {
console.log(`${signal} received, shutting down gracefully...`);
await app.shutdown();
process.exit(0);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
}
main().catch((err) => {
console.error('Failed to start app', err);
process.exit(1);
});Key Features:
createApp()- Initialize the Gati applicationloadHandlers()- Auto-discover and register handlers from a directoryapp.listen()- Start the HTTP serverapp.shutdown()- Graceful shutdown with cleanup- Environment variable support for PORT and HOST
Step 6: Start the Development Server
pnpm devYou should see:
Server running on 0.0.0.0:3000The dev server includes:
- ✅ Hot reload - Changes automatically restart the server
- ✅ File watching - Monitors
src/**/*.tsfor changes - ✅ Environment loading - Auto-loads
.envfiles - ✅ Error reporting - Clear error messages in development
Step 7: Test Your API
In another terminal:
# Test hello endpoint
curl http://localhost:3000/api/hello
# With query parameter
curl http://localhost:3000/api/hello?name=Alice
# Test health check
curl http://localhost:3000/healthResponse from /api/hello:
{
"message": "Hello, World!",
"timestamp": "2025-11-10T12:00:00.000Z"
}Response from /health:
{
"status": "healthy",
"timestamp": "2025-11-10T12:00:00.000Z",
"uptime": 42.5
}🎉 Congratulations! You've created your first Gati API endpoint!
Project Structure
Let's understand each part of your Gati application:
Handlers (src/handlers/)
Handlers are functions that process HTTP requests. Each handler follows this signature:
handler(req, res, gctx, lctx)- Purpose: Process requests and generate responses
- Example:
getUserHandler,createPostHandler - Learn more: Handler Development Guide
Modules (src/modules/)
Modules are reusable business logic components loaded via dependency injection:
export function initLogger(gctx: GlobalContext): Logger {
return {
log: (message) => console.log(message),
error: (message, error) => console.error(message, error),
};
}- Purpose: Shared functionality (database, cache, logger)
- Pattern: Initialized once, shared across requests
- Learn more: Module Creation Guide
Configuration (gati.config.ts)
The main configuration file for your application:
export default {
server: {
port: 3000, // Server port
host: 'localhost', // Server host
},
routes: [/* ... */], // Route definitions
modules: (gctx) => {}, // Module initialization
config: {/* ... */}, // App-level config
};Basic Concepts
1. Handlers
Handlers are pure functions that process requests:
const getUserHandler: Handler = async (req, res, gctx, lctx) => {
const userId = req.params.id;
const user = await gctx.modules['db'].findUser(userId);
res.json({ user });
};Key Points:
- Synchronous or asynchronous
- Access request data via
req - Send responses via
res - Use modules via
gctx.modules - Access request metadata via
lctx
2. Modules
Modules provide shared functionality:
// src/modules/database.ts
export function initDatabase(gctx: GlobalContext) {
const connection = createConnection(/* ... */);
gctx.lifecycle.onShutdown(() => {
connection.close();
});
return {
findUser: (id) => connection.query('SELECT * FROM users WHERE id = ?', [id]),
};
}
// gati.config.ts
modules: (gctx) => {
gctx.modules['db'] = initDatabase(gctx);
}Key Points:
- Initialized once at startup
- Shared across all requests
- Support lifecycle hooks (init, shutdown)
- Accessed via
gctx.modules
3. Context
Gati provides two types of context:
Global Context (gctx) - Shared across all requests:
gctx.modules- Module registrygctx.config- Application configurationgctx.state- Shared stategctx.lifecycle- Lifecycle hooks
Local Context (lctx) - Scoped to a single request:
lctx.requestId- Unique request identifierlctx.timestamp- Request timestamplctx.state- Request-scoped statelctx.lifecycle- Request lifecycle hooks
4. Routing
Define routes by mapping HTTP methods and paths to handlers:
routes: [
{ method: 'GET', path: '/users', handler: listUsersHandler },
{ method: 'GET', path: '/users/:id', handler: getUserHandler },
{ method: 'POST', path: '/users', handler: createUserHandler },
{ method: 'PUT', path: '/users/:id', handler: updateUserHandler },
{ method: 'DELETE', path: '/users/:id', handler: deleteUserHandler },
]Path Parameters:
- Use
:paramsyntax for dynamic segments - Access via
req.params.param
Query Parameters:
- Access via
req.query.param
Your First API Endpoint
Let's build a complete user API with CRUD operations:
Step 1: Create the Handler
Create src/handlers/user.ts:
import type { Handler } from 'gati';
import { HandlerError } from 'gati';
// Mock data
const users = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
];
// GET /users
export const listUsersHandler: Handler = (req, res) => {
const nameFilter = req.query.name as string | undefined;
let filtered = users;
if (nameFilter) {
filtered = users.filter(u =>
u.name.toLowerCase().includes(nameFilter.toLowerCase())
);
}
res.json({ users: filtered, count: filtered.length });
};
// GET /users/:id
export const getUserHandler: Handler = (req, res, gctx) => {
const userId = req.params.id;
// Use logger module
const logger = gctx.modules['logger'] as any;
logger?.log(`Fetching user ${userId}`);
const user = users.find(u => u.id === userId);
if (!user) {
throw new HandlerError('User not found', 404, { userId });
}
res.json({ user });
};
// POST /users
export const createUserHandler: Handler = (req, res) => {
const { name, email } = req.body as { name: string; email: string };
const newUser = {
id: String(users.length + 1),
name,
email,
};
users.push(newUser);
res.status(201).json({ user: newUser });
};Step 2: Add Routes
Update gati.config.ts:
import { helloHandler } from './src/handlers/hello';
import {
listUsersHandler,
getUserHandler,
createUserHandler
} from './src/handlers/user';
export default {
server: {
port: 3000,
host: 'localhost',
},
routes: [
{ method: 'GET', path: '/hello', handler: helloHandler },
{ method: 'GET', path: '/users', handler: listUsersHandler },
{ method: 'GET', path: '/users/:id', handler: getUserHandler },
{ method: 'POST', path: '/users', handler: createUserHandler },
],
modules: (gctx) => {
// Modules will be added later
},
};Step 3: Test Your Endpoints
# List all users
curl http://localhost:3000/users
# Get a specific user
curl http://localhost:3000/users/1
# Filter users by name
curl http://localhost:3000/users?name=Alice
# Create a new user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Charlie","email":"charlie@example.com"}'Running Your Application
Development Mode
Start with hot-reload:
pnpm devChanges to your code will automatically reload the server.
Production Build
Build for production:
pnpm buildStart the production server:
pnpm startEnvironment Variables
Create a .env file:
NODE_ENV=development
PORT=3000
HOST=localhost
DATABASE_URL=postgresql://localhost/mydbAccess in your code:
const dbUrl = process.env.DATABASE_URL;Next Steps
Now that you have a basic Gati application running, explore these topics:
Learn Handler Development
- Handler Development Guide
- Path parameters and query strings
- Request body validation
- Error handling patterns
- Response formatting
Create Modules
- Module Creation Guide
- Module lifecycle
- Dependency injection
- Testing strategies
- Best practices
Understand Architecture
- Architecture Documentation
- Component relationships
- Request flow
- Design decisions
- Extension points
Advanced Topics
- Middleware: Add custom request processing
- Validation: Use Zod or similar for input validation
- Authentication: Implement auth middleware
- Database: Integrate PostgreSQL, MongoDB, etc.
- Testing: Write unit and integration tests
- Deployment: Deploy to Kubernetes, AWS, or GCP
Troubleshooting
Port Already in Use
Error: EADDRINUSE: address already in use
Solution: Change the port in gati.config.ts:
server: {
port: 8080, // Use a different port
}Or kill the process using the port:
# Find the process
lsof -i :3000
# Kill it
kill -9 <PID>TypeScript Errors
Error: Module not found or type errors
Solution: Ensure dependencies are installed:
pnpm installCheck your tsconfig.json paths configuration:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}Module Not Loaded
Error: Module undefined or null in handlers
Solution: Ensure modules are initialized in gati.config.ts:
modules: (gctx) => {
gctx.modules['logger'] = initLogger(gctx);
gctx.modules['db'] = initDatabase(gctx);
}Hot Reload Not Working
Error: Changes not reflected after saving
Solution:
- Restart the dev server
- Check for syntax errors in your code
- Ensure files are in the
src/directory
Request Body is Undefined
Error: req.body is undefined
Solution: Gati automatically parses JSON bodies. Ensure:
- Content-Type header is
application/json - Request body is valid JSON
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'404 Not Found
Error: Route returns 404
Solution:
- Check route is registered in
gati.config.ts - Verify path matches exactly (case-sensitive)
- Ensure handler is imported correctly
import { myHandler } from './src/handlers/myHandler'; // Correct path
routes: [
{ method: 'GET', path: '/my-route', handler: myHandler },
]Additional Resources
- GitHub Repository: https://github.com/krishnapaul242/gati
- Example Projects: https://github.com/krishnapaul242/gati/tree/main/examples
- Issue Tracker: https://github.com/krishnapaul242/gati/issues
- Discussions: https://github.com/krishnapaul242/gati/discussions
Getting Help
- Community Discord: Join here (coming soon)
- Stack Overflow: Tag your questions with
gati-framework - GitHub Issues: For bug reports and feature requests
Next: Handler Development Guide →
See Also: