Karya Semi
HomeBlogSearchCategoriesAboutContact
Karya Semi

Less noise. More notes.

HomeBlogAboutContactPrivacy PolicyDisclaimer

© 2026 Karya Semi. All rights reserved.

XGitHubLinkedIn
  1. Home
  2. /Categories
  3. /Web Development

Typebase Delivers File-Based TypeScript Backend Architecture

Build light Node services fast. Use typebase typescript backend framework to simplify API routing and data storage in single folder.

Dian Rijal Asyrof/August 31, 2026/6 min read
Illustration for Typebase Delivers File-Based TypeScript Backend Architecture

TypeScript backend setup typically requires managing multiple configuration files: package managers, dependencies, tsconfig.json, bundlers, hot-reloading utilities, and routing boilerplates. Additionally, database integration introduces the need for Object-Relational Mappers (ORMs), schema migrations, and connection pool management.

This overhead slows down the development of microservices, webhooks, and mobile backends.

Typebase bypasses this setup phase. It uses file-based routing and integrated storage to let you build APIs from a single folder with zero initial configuration.

The Single-Folder Architecture

Typebase eliminates controllers, services, and repositories. The folder structure itself defines the API endpoints and the data models.

Consider this directory structure:

my-service/
├── api/
│   ├── users/
│   │   ├── get.ts
│   │   └── post.ts
│   ├── items/
│   │   ├── [id]/
│   │   │   └── get.ts
│   │   └── get.ts
│   └── status.ts
└── data/
    └── users.json

The api/ directory holds the route handlers. The file path maps directly to the URL path, and the filename maps to the HTTP method. For example:

  • api/users/post.ts handles POST /users
  • api/items/[id]/get.ts handles GET /items/:id
  • api/status.ts handles GET /status (defaulting to GET when no method is specified in the filename)

The data/ directory holds the application state. You do not need to run a separate database server. Typebase manages local JSON files or SQLite databases mapped directly to TypeScript interfaces.

To start the development server, run:

npx typebase dev

The engine reads the directory structure, starts the HTTP server, and watches for file changes. Route edits and new files are registered instantly without restarting the process.

Route Resolution Algorithm

Under the hood, Typebase parses the api/ directory at startup and builds a route tree. When an HTTP request arrives, the engine matches the request path against the tree using the following priority rules:

  1. Exact Matches: Static paths like api/items/get.ts take precedence.
  2. Dynamic Matches: Parameterized paths like api/items/[id]/get.ts match if no static route matches.
  3. Catch-All Matches: Paths like api/items/[...catchall].ts match any remaining sub-paths.

Here is a simplified representation of the internal route matching logic:

function matchRoute(requestPath: string, method: string, routes: Route[]) {
  const segments = requestPath.split('/').filter(Boolean);
  
  for (const route of routes) {
    if (route.method !== method) continue;
    
    const routeSegments = route.path.split('/').filter(Boolean);
    if (routeSegments.length !== segments.length && !route.isCatchAll) continue;
    
    const params: Record<string, string> = {};
    let match = true;
    
    for (let i = 0; i < routeSegments.length; i++) {
      const routeSeg = routeSegments[i];
      const reqSeg = segments[i];
      
      if (routeSeg.startsWith('[') && routeSeg.endsWith(']')) {
        const paramName = routeSeg.slice(1, -1);
        params[paramName] = reqSeg;
      } else if (routeSeg !== reqSeg) {
        match = false;
        break;
      }
    }
    
    if (match) {
      return { handler: route.handler, params };
    }
  }
  return null;
}

Writing Route Handlers

Handlers export a default asynchronous function. The engine passes request and response objects to this function.

Example api/users/get.ts:

import { Request, Response } from 'typebase';
 
interface User {
  id: string;
  name: string;
  role: string;
}
 
export default async function handler(req: Request, res: Response) {
  const users: User[] = [
    { id: '1', name: 'Alice', role: 'admin' },
    { id: '2', name: 'Bob', role: 'user' }
  ];
 
  return res.json(users);
}

You do not need to import Express, configure middleware, or maintain a central route registry. The presence of the file registers the endpoint.

Dynamic paths use brackets. Example api/items/[id]/get.ts:

import { Request, Response } from 'typebase';
 
export default async function handler(req: Request, res: Response) {
  const itemId = req.params.id;
  
  if (!itemId) {
    return res.status(400).json({ error: 'Missing item ID' });
  }
 
  return res.json({
    id: itemId,
    name: `Item ${itemId}`,
    status: 'active'
  });
}

The engine parses the incoming URL, extracts the parameters, and populates the req.params object before invoking the handler.

Middleware and Request Lifecycle

Typebase supports route-specific middleware by exporting a middleware array from the route file. The engine executes these functions sequentially before running the main handler.

Example of a protected route in api/admin/get.ts:

import { Request, Response, NextFunction } from 'typebase';
 
export const middleware = [
  async function authenticate(req: Request, res: Response, next: NextFunction) {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
    
    const token = authHeader.split(' ')[1];
    if (token !== 'secret-token') {
      return res.status(403).json({ error: 'Forbidden' });
    }
    
    next();
  }
];
 
export default async function handler(req: Request, res: Response) {
  return res.json({ status: 'secure-data' });
}

Input Validation at the Edge

Typebase includes built-in schema validation. You define the validation schema directly in the route file. The engine validates the request body, query parameters, and headers before the handler runs.

Example api/users/post.ts:

import { Request, Response, Schema } from 'typebase';
 
export const schema: Schema = {
  body: {
    name: { type: 'string', required: true, minLength: 2 },
    email: { type: 'string', required: true, format: 'email' },
    age: { type: 'number', required: false },
    profile: {
      type: 'object',
      required: true,
      properties: {
        bio: { type: 'string', maxLength: 160 }
      }
    }
  }
};
 
interface CreateUserBody {
  name: string;
  email: string;
  age?: number;
  profile: {
    bio: string;
  };
}
 
export default async function handler(req: Request<CreateUserBody>, res: Response) {
  const { name, email, age, profile } = req.body;
 
  const newUser = {
    id: crypto.randomUUID(),
    name,
    email,
    age,
    profile
  };
 
  return res.status(201).json(newUser);
}

If a request fails validation, the engine returns a 400 Bad Request status code with a structured error payload:

{
  "error": "Validation Failed",
  "details": [
    { "field": "body.email", "message": "Value must be a valid email address" },
    { "field": "body.profile", "message": "Required field is missing" }
  ]
}

The handler function only runs if the incoming data matches the defined schema.

File-Based Data Storage

Typebase includes an integrated data store. By default, it saves data to local JSON files. For larger datasets or transactional workloads, you can configure it to use SQLite.

Example of using the database API:

import { Request, Response, db } from 'typebase';
 
interface Task {
  id: string;
  title: string;
  completed: boolean;
  createdAt: string;
}
 
export default async function handler(req: Request, res: Response) {
  const tasksCollection = await db.collection<Task>('tasks');
 
  if (req.method === 'GET') {
    const allTasks = await tasksCollection.find({ completed: false });
    return res.json(allTasks);
  }
 
  if (req.method === 'POST') {
    const body = req.body as { title: string };
    
    if (!body.title) {
      return res.status(400).json({ error: 'Title is required' });
    }
 
    const newTask = await tasksCollection.insert({
      id: crypto.randomUUID(),
      title: body.title,
      completed: false,
      createdAt: new Date().toISOString()
    });
 
    return res.status(201).json(newTask);
  }
}

Calling db.collection<Task>('tasks') initializes the collection. If the file data/tasks.json does not exist, Typebase creates it.

Concurrency and Storage Drivers

To prevent data corruption during concurrent writes, Typebase routes all write operations through an in-memory queue. This queue serializes disk writes, ensuring that concurrent requests do not overwrite each other's changes.

For production workloads, you can switch the storage driver to SQLite by creating a typebase.config.json file in the root directory:

{
  "database": {
    "driver": "sqlite",
    "storage": "data/production.sqlite",
    "wal": true
  }
}

Enabling Write-Ahead Logging (WAL) mode allows concurrent reads to proceed while a write operation is in progress, improving throughput.

How Typebase Runs TypeScript Without a Build Step

Node.js cannot execute TypeScript files directly without an external compilation step. Typebase solves this by wrapping esbuild.

During development, when a request hits a route:

  1. The engine checks if the handler file has changed since the last compilation.
  2. If changed, esbuild compiles the file in memory.
  3. The compiled JavaScript is loaded using a dynamic import.
  4. The result is cached in memory for subsequent requests.

This process keeps startup times low and ensures that changes to route files are reflected immediately on the next request.

For production deployments, compile the project beforehand:

npx typebase build

This command compiles all routes, bundles external dependencies, and outputs the optimized assets to the dist/ directory. You can then run the production server using standard Node.js:

node dist/server.js

This build step keeps the production container small, applying principles similar to frontend bundle optimization, and removes the need to ship esbuild or TypeScript compilers to the production environment.

Comparing Typebase with Other Frameworks

FeatureTypebaseExpress + TSNext.js API Routes
RoutingFile-basedCode-basedFile-based
Setup TimeZeroHighMedium
Built-in DBYesNoNo
ExecutionOn-demand TSBuild requiredNext.js dev server
Bundle SizeMinimalVariableLarge (Next.js core)

Next.js API routes carry a large dependency footprint because they are optimized for React applications, even when utilizing features like Next.js 15 partial prerendering. Express requires manual routing setup, server configuration, and TypeScript build pipelines. Typebase provides file-based routing with a minimal runtime footprint and built-in storage.

Limitations and Trade-offs

Typebase is designed for small to medium services. It has specific limitations:

  • Complex Middleware: Global middleware chains and complex request lifecycles are harder to manage without a central application registry.
  • Query Performance: The built-in JSON database does not support complex queries, deep nesting, or ACID transactions across multiple collections. For these use cases, connect to an external database like PostgreSQL or MySQL.
  • Cold Starts: The first request to a route during development incurs a 10 to 50 ms latency penalty due to on-the-fly compilation. Subsequent requests run at native speed.

Deploying a Typebase Service

To deploy a Typebase service, copy the project directory to your hosting provider.

Here is a production-ready Dockerfile:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npx typebase build
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "dist/server.js"]

If you use the built-in file storage, mount a persistent volume to the /app/data directory to prevent data loss when the container restarts. For serverless deployments, configure the application to use an external database.

DR

Dian Rijal Asyrof

Writes about useful AI tools, programming practice, and the craft of building reliable software.

Previous articleBoot Virtual iOS Instances Using Apple Virtualization Framework CLINext articleSQLite as a Production Document Store Using Native JSON Functions
TypebaseTypeScriptFile BasedArchitectureDatabase
On this page↓
  1. The Single-Folder Architecture
  2. Route Resolution Algorithm
  3. Writing Route Handlers
  4. Middleware and Request Lifecycle
  5. Input Validation at the Edge
  6. File-Based Data Storage
  7. Concurrency and Storage Drivers
  8. How Typebase Runs TypeScript Without a Build Step
  9. Comparing Typebase with Other Frameworks
  10. Limitations and Trade-offs
  11. Deploying a Typebase Service

On this page

  1. The Single-Folder Architecture
  2. Route Resolution Algorithm
  3. Writing Route Handlers
  4. Middleware and Request Lifecycle
  5. Input Validation at the Edge
  6. File-Based Data Storage
  7. Concurrency and Storage Drivers
  8. How Typebase Runs TypeScript Without a Build Step
  9. Comparing Typebase with Other Frameworks
  10. Limitations and Trade-offs
  11. Deploying a Typebase Service

See also

Illustration for Postgres Transactions Are a Hidden Superpower for Distributed Systems
Software Engineering/Jul 3, 2026

Postgres Transactions Are a Hidden Superpower for Distributed Systems

Before reaching for Kafka, Redis, or a dedicated message queue, consider what Postgres can already do. The database most teams already use has surprisingly powerful coordination primitives built in.

3 min read
PostgresDatabase
Illustration for Mythic Unveils Analog Compute In Memory Architecture For AI Inference
Technology/Aug 28, 2026

Mythic Unveils Analog Compute In Memory Architecture For AI Inference

Run neural networks directly inside flash memory arrays. Use mythic analog compute memory to slash edge AI power draw and latency.

6 min read
MythicAnalog
Illustration for How Bounding Database Reads Silently Broke Primary Application Features
Software Engineering/Aug 28, 2026

How Bounding Database Reads Silently Broke Primary Application Features

Database optimization bug postmortem. Bad query limit broke production analyzer. Silent failure bypassed automated unit tests. Fix query bounds.

9 min read
CursorPostgreSQL