Skip to content

Core Philosophy

Gati is built on five foundational principles that guide every design decision and feature implementation.


1. Let Developers Write Business Logic

"The best code is the code you don't have to write."

The Problem

Traditional backend development forces developers to write:

  • Infrastructure code (Docker, K8s manifests, CI/CD)
  • Boilerplate (routing, middleware, validation)
  • Configuration (YAML, env vars, feature flags)
  • Deployment scripts (build, test, deploy pipelines)
  • Monitoring setup (logging, metrics, alerting)

Result: 70% infrastructure, 30% business logic.

The Gati Solution

Gati analyzes your code and auto-generates everything else:

typescript
// You write ONLY this
type CreateUser = {
  email: EmailString;
  password: string & MinLen<8>;
};

export const createUserHandler: Handler = async (req, res, gctx, lctx) => {
  const { email, password } = req.body; // Auto-validated
  const user = await gctx.modules['database']?.createUser({ email, password });
  res.json({ user });
};

// Gati generates:
// - Runtime validator
// - OpenAPI spec
// - TypeScript/Python/Go SDKs
// - Docker + K8s manifests
// - Deployment pipeline
// - Monitoring config
// - Health checks
// - Timescape version metadata

Result: 95% business logic, 5% configuration.


2. APIs That Never Break

"Ship new versions without fear. Old clients work seamlessly."

The Problem

Every backend eventually becomes unmaintainable:

Timeline of Pain:
─────────────────────────────────────────────────────────
v1.0: Launches successfully
v1.5: Added new fields (non-breaking)
v2.0: Renamed field 'name' → 'firstName' + 'lastName' (BREAKING!)
      - Old mobile apps still use 'name'
      - Backend must support both formats
      - Manual transformation layer added
v3.0: Changed response structure (BREAKING!)
      - v1, v2, v3 all running in parallel
      - Spaghetti code trying to maintain compatibility
      - Fear of making any changes
v4.0: Complete rewrite required (months of work)

The Gati Solution (Timescape)

Automatic version management:

typescript
// Version 1 (2024-11-01)
type User = {
  name: string;
  email: EmailString;
};

// Version 2 (2024-11-15) - Breaking change detected
type User = {
  firstName: string;
  lastName: string;
  email: EmailString;
};

// Gati auto-generates transformer (AI-assisted)
export const transformV1toV2 = (v1: UserV1): UserV2 => ({
  firstName: v1.name.split(' ')[0],
  lastName: v1.name.split(' ')[1] || '',
  email: v1.email,
});

// Both versions run in parallel automatically
// Old clients: GET /users/123 → v1 response (via transformer)
// New clients: GET /users/123 → v2 response
// Zero manual work required

Timescape features:

  • ✔ Automatic version creation on schema changes
  • ✔ Parallel execution (v1, v2, v3 run simultaneously)
  • ✔ Automatic schema diffing
  • ✔ Auto-generated transformers (AI-assisted)
  • ✔ Gradual rollout and deprecation
  • ✔ Zero-downtime version switching

3. Modules Like NPM Packages

"Install modules the way frontend developers install packages."

The Problem

Backend dependencies are complex:

bash
# Traditional approach (PostgreSQL example)
1. Install PostgreSQL server
2. Configure connection pooling
3. Set up environment variables
4. Write database module
5. Add health checks
6. Configure auto-scaling
7. Set up backups
8. Monitor connection pool

The Gati Solution

Install and configure in seconds:

bash
# Install module
pnpm add @gati-modules/postgresql

# Configure
# gati.config.ts
export default {
  modules: {
    database: {
      type: '@gati-modules/postgresql',
      config: {
        url: process.env.DATABASE_URL,
        poolMin: 2,
        poolMax: 10
      }
    }
  }
}

# Use in handlers
export const handler: Handler = async (req, res, gctx) => {
  const users = await gctx.modules['database']?.query('SELECT * FROM users');
  res.json({ users });
};

Module types:

  • Code modules — TypeScript/JavaScript packages
  • NPM modules — Published packages (e.g., @gati-modules/redis)
  • Docker modules — Containerized services (Rust, Go, Python)
  • Binary modules — Compiled executables (called via process spawn)

Module benefits:

  • ✅ Isolated processes (independent scaling)
  • ✅ Own manifests and contracts
  • ✅ Automatic health checks
  • ✅ Lifecycle hooks (init, shutdown, reload)
  • ✅ Version compatibility metadata
  • ✅ Marketplace distribution

4. TypeScript-Native Types

"Write types once, use everywhere."

The Problem (Other Frameworks)

Duplicate schemas across:

  • TypeScript types (for compile-time checking)
  • Runtime validators (Zod, Yup, class-validator)
  • OpenAPI specs (for documentation)
  • Client SDKs (manually written or codegen)
  • Database schemas (Prisma, TypeORM)

Example:

typescript
// 1. TypeScript type
type CreateUser = {
  email: string;
  password: string;
  age?: number;
};

// 2. Runtime validator (Zod)
const CreateUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  age: z.number().min(18).max(100).optional(),
});

// 3. OpenAPI spec (manually maintained)
// components:
//   schemas:
//     CreateUser:
//       type: object
//       properties:
//         email: { type: string, format: email }
//         password: { type: string, minLength: 8 }
//         age: { type: number, minimum: 18, maximum: 100 }

// Must keep all three in sync manually!

The Gati Solution

Single definition with branded types:

typescript
// Write ONCE
type CreateUser = {
  email: EmailString;
  password: string & MinLen<8>;
  age?: number & Min<18> & Max<100>;
};

// Gati automatically generates:
// ✅ Runtime validator (Ajv-level performance)
// ✅ GType schema (manifest metadata)
// ✅ OpenAPI specification
// ✅ TypeScript/Python/Go SDKs
// ✅ Timescape version metadata
// ✅ Transformer generation hints
// ✅ Playground request template
// ✅ Test fixtures and mocks

Branded types library:

typescript
// Strings
EmailString, URLString, PhoneString, UUIDString
PasswordString, JWTString, HexString, Base64String

// Numbers
PositiveNumber, NegativeNumber, IntegerNumber
Percentage, PortNumber, PriceInCents

// Constraint combinators
MinLen<N>, MaxLen<N>, Pattern<"regex">
Min<N>, Max<N>, MultipleOf<N>
Enum<T>, UniqueItems, NonEmpty

5. Zero-Ops Deployment

"One command to deploy anywhere. Gati handles the rest."

The Problem

Traditional deployment checklist:

☐ Write Dockerfile (multi-stage, optimized)
☐ Build Docker image
☐ Push to container registry
☐ Write Kubernetes manifests:
  ☐ Deployment
  ☐ Service
  ☐ ConfigMap
  ☐ Secret
  ☐ Ingress
  ☐ HPA (auto-scaling)
  ☐ PodDisruptionBudget
  ☐ ServiceAccount
  ☐ RBAC rules
☐ Set up CI/CD pipeline
☐ Configure load balancer
☐ Set up SSL certificates
☐ Configure DNS
☐ Set up monitoring/alerting
☐ Configure logging pipeline
☐ Test deployment
☐ Set up rollback mechanism
☐ Deploy and pray

Time: 2-4 weeks for first deployment, 1-2 days for subsequent deployments.

The Gati Solution

bash
# Deploy to local Kubernetes
gati deploy dev --local

# Deploy to AWS EKS
gati deploy production --provider aws --region us-east-1

# Deploy to GCP GKE (M2+)
gati deploy production --provider gcp --region us-central1

# Deploy to Azure AKS (M2+)
gati deploy production --provider azure --region eastus

Time: 5-10 minutes for first deployment, 2-3 minutes for updates.

Gati handles:

TaskTraditionalGati
Container registryManual setupAutomatic
K8s clusterManual provisioningAuto-provision or use existing
Load balancerManual configAutomatic
SSL/TLSManual cert managementAuto-provision + renewal
DNSManual setupAutomatic
SecretsManual K8s secretsEncrypted, auto-injected
Auto-scalingManual HPA/VPAAutomatic based on metrics
MonitoringManual setupBuilt-in (/_control panel)
LoggingManual pipelineStructured, searchable
RollbackManual kubectlgati rollback command

Design Principles

These five core philosophies translate into concrete design principles:

Convention over Configuration

  • File-based routing: src/handlers/users/[id].ts/users/:id
  • Sensible defaults: Port 3000, JSON responses, CORS enabled
  • Minimal config: gati.config.ts only for customization

Progressive Enhancement

  • Start simple: Single handler file gets you started
  • Add complexity only when needed: Modules, middleware, plugins
  • No premature abstraction: Write code, refactor later

Cloud-Native First

  • Built for Kubernetes: Not bolted on, fundamentally designed for it
  • Stateless by default: Handlers pure functions, state in modules
  • 12-Factor compliant: Config via env vars, logs to stdout, etc.

Developer Experience First

  • Fast feedback: Hot reload in 50-200ms
  • Clear errors: Actionable error messages with context
  • Great tooling: CLI, Playground, Control Panel
  • Comprehensive docs: Examples, guides, API reference

Open and Extensible

  • Plugin ecosystem: Marketplace for modules and plugins
  • No vendor lock-in: Deploy anywhere (AWS, GCP, Azure, on-prem)
  • MIT license: Free to use, modify, distribute
  • Community-driven: Open to contributions and feedback

Philosophy in Action

Example: Migrating from Express

Before (Express.js):

typescript
// app.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { createLogger } from 'winston';
import { Pool } from 'pg';

const app = express();
const logger = createLogger({ /* config */ });
const db = new Pool({ connectionString: process.env.DATABASE_URL });

app.use(cors());
app.use(helmet());
app.use(express.json());

app.get('/users/:id', async (req, res) => {
  try {
    const result = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.json({ user: result.rows[0] });
  } catch (error) {
    logger.error('Error fetching user:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(3000);

// Separate files:
// - Dockerfile
// - docker-compose.yml
// - k8s/deployment.yaml
// - k8s/service.yaml
// - k8s/ingress.yaml
// - .github/workflows/deploy.yml

After (Gati):

typescript
// src/handlers/users/[id].ts
import type { Handler } from '@gati-framework/runtime';
import { HandlerError } from '@gati-framework/runtime';

export const METHOD = 'GET';

export const getUserHandler: Handler = async (req, res, gctx, lctx) => {
  const userId = req.params.id;
  const user = await gctx.modules['database']?.findUser(userId);
  
  if (!user) {
    throw new HandlerError('User not found', 404, { userId });
  }
  
  res.json({ user });
};

// gati.config.ts (only configuration needed)
export default {
  modules: {
    database: {
      type: '@gati-modules/postgresql',
      config: { url: process.env.DATABASE_URL }
    }
  }
};

// Deploy:
// gati deploy production --provider aws

Lines of code:

  • Express: ~100 lines (handler + config + Docker + K8s manifests + CI/CD)
  • Gati: ~20 lines (handler + config)

80% reduction in code. 100% of the functionality.


Next Steps


Last Updated: November 18, 2025

Released under the MIT License.