Vite plugin for file-based HTTP API generation
npm install vitek-plugin
---
Vitek is a portmanteau combining "Vite" (the build tool) and "tek" (a suffix suggesting technology/toolkit). The name reflects the plugin's core purpose: bringing powerful API generation capabilities to the Vite ecosystem.
The "tek" suffix is commonly used in technology naming to denote tools and frameworks (similar to "kit" or "toolkit"), making "Vitek" a natural fit for a plugin that extends Vite's functionality with file-based API routing and type generation.
---
Vitek is a powerful Vite plugin that enables automatic file-based HTTP API generation. Write your API endpoints as simple TypeScript/JavaScript files, and Vitek handles all the configuration, type generation, and integration with the Vite development server.
- š Zero Configuration: Works out of the box with Vite - no separate server setup needed
- š File-Based Routing: Organize your API like your file system - intuitive and scalable
- š„ Hot Reload: Automatic reloading of routes and middlewares on file changes
- šŖ Type-Safe: Full TypeScript support with auto-generated types and client helpers
- šÆ Developer Experience: Generated client helpers for seamless frontend integration
- šļø Modular Architecture: Core logic is runtime-agnostic, ready for other environments
- ā” Lightweight: Minimal overhead, runs directly in Vite's dev server
- š Unified Development: Same server for frontend and backend during development
- š¦ No Dependencies: Works with your existing Vite setup without additional runtime dependencies
---
#### Next.js API Routes
- Next.js: Requires Next.js framework, tightly coupled to React ecosystem
- Vitek: Framework-agnostic, works with any frontend (React, Vue, Svelte, vanilla JS)
- Vitek Advantage: More flexible, lighter weight, not tied to a specific framework
#### SvelteKit API Routes
- SvelteKit: Requires SvelteKit framework, Svelte-specific
- Vitek: Works with any frontend framework or no framework at all
- Vitek Advantage: Universal solution, not limited to Svelte projects
#### Nuxt.js Server Routes
- Nuxt.js: Vue.js-specific, requires Nuxt framework
- Vitek: Framework-independent, works with any stack
- Vitek Advantage: More versatile, can be used in any Vite project
#### Express.js / Fastify
- Express/Fastify: Separate Node.js server, requires server setup and configuration
- Vitek: Integrated with Vite dev server, no separate server needed
- Vitek Advantage: Simpler setup, unified development environment, automatic hot reload
#### Hono
- Hono: Fast web framework, but still requires separate server setup
- Vitek: File-based routing with automatic type generation, integrated with Vite
- Vitek Advantage: Better DX with file-based routing, automatic client generation
#### tRPC
- tRPC: Type-safe RPC framework, requires separate server setup
- Vitek: File-based REST API with automatic type generation, integrated with Vite
- Vitek Advantage: Simpler setup, standard REST API, works with any HTTP client
#### Remix
- Remix: Full-stack React framework with server-side rendering
- Vitek: Lightweight API layer, works with any frontend
- Vitek Advantage: More flexible, lighter, not tied to React or SSR
Choose Vitek if you:
- ā
Want to add API routes to an existing Vite project
- ā
Prefer file-based routing over manual route registration
- ā
Want automatic type generation for your API
- ā
Need a lightweight solution without framework lock-in
- ā
Want unified development (frontend + backend in one server)
- ā
Prefer REST APIs over RPC or GraphQL
- ā
Want zero configuration and minimal setup
Consider alternatives if you:
- Need server-side rendering (use Next.js, Remix, or SvelteKit)
- Want GraphQL (use Apollo Server or similar)
- Need advanced features like WebSockets out of the box (use Express/Fastify with Socket.io)
- Are building a full-stack framework from scratch (use Hono, Express, or Fastify)
- Need production-ready server features (use Express, Fastify, or Hono with proper setup)
Vitek's sweet spot: Adding API routes to Vite projects with minimal configuration, automatic type safety, and excellent developer experience.
---
- ā
File-based routing: Automatic routes based on file structure
- ā
Zero config: Works automatically with the Vite server
- ā
Hot reload: Automatically reloads routes and middlewares on save
- ā
Type-safe: Automatically generates TypeScript types for all routes
- ā
Client helpers: Generates typed functions to call the API from the frontend
- ā
Hierarchical middleware: Middlewares per folder/subfolder
- ā
All HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
- ā
Dynamic parameters: Support for [id] and [...ids] (catch-all)
- ā
Typed query params: Define types for query parameters
- ā
Typed body: Define types for request body
- ā
JavaScript support: Works with both TypeScript and JavaScript projects
- ā
Isolated core: Modular architecture, ready for other runtimes
- ā
Response control: Custom status codes and headers via response helpers
- ā
HTTP error classes: Built-in error classes with automatic status code mapping
- ā
Request validation: Optional runtime validation for body and query parameters
- ā
Enhanced logging: Configurable log levels and request/response logging
---
``bash`
npm install vitek-pluginor
pnpm add vitek-pluginor
yarn add vitek-plugin
Requirements:
- Vite ^5.0.0
- Node.js 18+ (for development)
---
Add Vitek to your vite.config.ts:
`typescript
import { defineConfig } from "vite";
import { vitek } from "vitek-plugin";
export default defineConfig({
plugins: [vitek()],
});
`
Create src/api/health.get.ts:
`typescript
import type { VitekContext } from "vitek-plugin";
export default function handler(context: VitekContext) {
return {
status: "ok",
timestamp: new Date().toISOString(),
};
}
`
`bash`
npm run dev
Visit: http://localhost:5173/api/health š
---
Files follow the pattern: [name].[method].ts or [name].[method].js
``
src/api/
āāā middleware.ts # Global middleware (optional)
āāā health.get.ts # GET /api/health
āāā users/
ā āāā middleware.ts # Middleware for /api/users/*
ā āāā [id].get.ts # GET /api/users/:id
ā āāā [id].put.ts # PUT /api/users/:id
ā āāā [id]/
ā āāā middleware.ts # Middleware for /api/users/:id/*
ā āāā posts.get.ts # GET /api/users/:id/posts
āāā posts/
āāā index.get.ts # GET /api/posts
āāā index.post.ts # POST /api/posts
āāā [id].get.ts # GET /api/posts/:id
āāā [...ids].get.ts # GET /api/posts/*ids (catch-all)
- Single parameter: [id].get.ts ā :idusers/[id].get.ts
- Example: ā /api/users/:id[...ids].get.ts
- Catch-all: ā *idsposts/[...ids].get.ts
- Example: ā /api/posts/*ids/api/posts/1/2/3
- Captures: ā params.ids = "1/2/3"
All HTTP methods are supported through file extension:
- .get.ts ā GET.post.ts
- ā POST.put.ts
- ā PUT.patch.ts
- ā PATCH.delete.ts
- ā DELETE.head.ts
- ā HEAD.options.ts
- ā OPTIONS
---
`typescript
// src/api/health.get.ts
import type { VitekContext } from "vitek-plugin";
export default function handler(context: VitekContext) {
return {
status: "ok",
timestamp: new Date().toISOString(),
};
}
`
`typescript
// src/api/users/[id].get.ts
import type { VitekContext } from "vitek-plugin";
export default async function handler(context: VitekContext) {
const { params } = context;
return {
id: params.id,
name: User ${params.id},user${params.id}@example.com
email: ,`
};
}
`typescript
// src/api/posts/index.post.ts
import type { VitekContext } from "vitek-plugin";
export type Body = {
title: string;
content: string;
authorId: number;
tags?: string[];
};
export default async function handler(context: VitekContext) {
const { body } = context;
return {
message: "Post created",
post: {
id: Math.random(),
...body,
createdAt: new Date().toISOString(),
},
};
}
`
`typescript
// src/api/posts/index.get.ts
import type { VitekContext } from "vitek-plugin";
export type Query = {
limit?: number;
offset?: number;
};
export default async function handler(context: VitekContext) {
const { query } = context;
const limit = query.limit ? Number(query.limit) : 10;
const offset = query.offset ? Number(query.offset) : 0;
return {
posts: [],
pagination: { limit, offset },
};
}
`
`typescript
// src/api/posts/[id]/comments.post.ts
import type { VitekContext } from "vitek-plugin";
export type Body = {
text: string;
authorId: number;
};
export type Query = {
notify?: boolean;
sendEmail?: boolean;
};
export default async function handler(context: VitekContext) {
const { params, body, query } = context;
return {
message: "Comment created",
postId: params.id,
comment: {
id: Math.random(),
...body,
postId: params.id,
notify: query.notify || false,
},
};
}
`
`typescript
// src/api/posts/[...ids].get.ts
import type { VitekContext } from "vitek-plugin";
export default async function handler(context: VitekContext) {
const { params } = context;
// params.ids contains all segments: "1/2/3" for /api/posts/1/2/3
const ids = params.ids.split("/");
return {
ids,
message: "Multiple posts requested",
};
}
`
---
Create src/api/middleware.ts to apply middlewares to all routes:
`typescript
// src/api/middleware.ts
import type { Middleware } from "vitek-plugin";
export default [
async (context, next) => {
// Executes before the route
console.log([Global] ${context.method} ${context.path});
await next(); // Continues to next middleware/handler
// Executes after the route
console.log([Global] Completed ${context.path});`
},
] satisfies Middleware[];
Middlewares are applied hierarchically based on folder structure:
`typescript
// src/api/posts/middleware.ts
// Applies to: /api/posts, /api/posts/:id, /api/posts/:id/comments, etc.
import type { Middleware } from "vitek-plugin";
export default [
async (context, next) => {
console.log([Posts Middleware] ${context.method} ${context.path});`
await next();
},
] satisfies Middleware[];
`typescript
// src/api/posts/[id]/middleware.ts
// Applies to: /api/posts/:id/comments, but NOT /api/posts
import type { Middleware } from "vitek-plugin";
export default [
async (context, next) => {
// Validate post exists
const postId = context.params.id;
console.log([Post ID Middleware] Validating post ${postId});`
await next();
},
] satisfies Middleware[];
Middleware execution order:
1. Global middleware (src/api/middleware.ts)src/api/posts/middleware.ts
2. Folder-specific middleware (e.g., )src/api/posts/[id]/middleware.ts
3. Nested folder middleware (e.g., )
4. Route handler
---
Vitek provides flexible response handling with support for custom status codes and headers.
Returning a plain object automatically creates a 200 OK JSON response:
`typescript`
export default function handler(context: VitekContext) {
return { message: "Success" }; // Status 200, JSON
}
Use response helpers for full control over HTTP responses:
`typescript
import { created, notFound, json } from "vitek-plugin";
export default function handler(context: VitekContext) {
// 201 Created
return created({ id: 123, message: "Resource created" });
// 404 Not Found
return notFound({ error: "Resource not found" });
// Custom status and headers
return json(
{ data: "custom" },
{
status: 201,
headers: { "X-Custom-Header": "value" },
}
);
}
`
- ok(body, headers?) - 200 OKcreated(body, headers?)
- - 201 CreatednoContent(headers?)
- - 204 No ContentbadRequest(body, headers?)
- - 400 Bad Requestunauthorized(body, headers?)
- - 401 Unauthorizedforbidden(body, headers?)
- - 403 ForbiddennotFound(body, headers?)
- - 404 Not Foundconflict(body, headers?)
- - 409 ConflictunprocessableEntity(body, headers?)
- - 422 Validation ErrortooManyRequests(body, headers?)
- - 429 Too Many RequestsinternalServerError(body, headers?)
- - 500 Internal Server Errorredirect(url, permanent?, preserveMethod?)
- - 301/302/307/308 Redirectjson(body, options?)
- - Custom JSON response with status and headers
---
Vitek provides HTTP error classes for better error handling with automatic status code mapping.
`typescript
import {
BadRequestError,
NotFoundError,
UnauthorizedError,
ValidationError,
} from "vitek-plugin";
export default function handler(context: VitekContext) {
const { params } = context;
if (!params.id) {
throw new BadRequestError("ID is required"); // Returns 400
}
const resource = findResource(params.id);
if (!resource) {
throw new NotFoundError("Resource not found"); // Returns 404
}
// Validation errors with field details
throw new ValidationError("Validation failed", {
email: ["Invalid email format"],
age: ["Must be 18 or older"],
}); // Returns 422
}
`
- BadRequestError - 400 Bad RequestUnauthorizedError
- - 401 UnauthorizedForbiddenError
- - 403 ForbiddenNotFoundError
- - 404 Not FoundConflictError
- - 409 ConflictValidationError
- - 422 Unprocessable Entity (with field errors)TooManyRequestsError
- - 429 Too Many RequestsInternalServerError
- - 500 Internal Server Error
All errors automatically return the appropriate HTTP status code and JSON error response.
---
Vitek provides optional runtime validation for request body and query parameters.
Use validation helpers in your handlers:
`typescript
import { validateBody, validateQuery, ValidationError } from "vitek-plugin";
import type { ValidationSchema } from "vitek-plugin";
export type Body = {
title: string;
content: string;
authorId: number;
};
export default function handler(context: VitekContext) {
// Validate body
const body = validateBody(context.body, {
title: { type: "string", required: true, min: 1, max: 200 },
content: { type: "string", required: true, min: 10 },
authorId: { type: "number", required: true, min: 1 },
});
// Validate query
const query = validateQuery(context.query, {
limit: { type: "number", min: 1, max: 100 },
offset: { type: "number", min: 0 },
});
// If validation fails, ValidationError (422) is thrown automatically
return { message: "Validated successfully", body, query };
}
`
`typescript`
type ValidationRule = {
type: "string" | "number" | "boolean" | "object" | "array";
required?: boolean;
min?: number; // For strings: min length, for numbers: min value, for arrays: min items
max?: number; // For strings: max length, for numbers: max value, for arrays: max items
pattern?: string | RegExp; // For strings: regex pattern
custom?: (value: any) => boolean | string; // Custom validator
};
- validate(data, schema) - Returns validation result without throwingvalidateOrThrow(data, schema)
- - Throws ValidationError if invalidvalidateBody(body, schema)
- - Validates request bodyvalidateQuery(query, schema)
- - Validates query parameters
---
Vitek automatically generates TypeScript types for all your routes.
Contains all route types and parameter definitions:
`typescript
// Auto-generated by Vitek - DO NOT EDIT
export type VitekParams = Record
export type VitekQuery = Record
export interface UsersIdGetParams extends VitekParams {
id: string;
}
export type PostsPostBody = {
title: string;
content: string;
authorId: number;
tags?: string[];
};
export type PostsIndexGetQuery = {
limit?: number;
offset?: number;
};
// Union type with all routes
export type VitekRoute =
| { pattern: "users/:id"; method: "get"; params: UsersIdGetParams }
| { pattern: "posts"; method: "post"; params: PostsPostBody };
// ...
`
Contains typed helper functions to call the API from the frontend:
`typescript
import { getUsersById, postPosts, getPostsIdComments } from "./api.services";
// GET /api/users/:id
const user = await getUsersById({ id: "123" });
// POST /api/posts (with body)
const post = await postPosts({
title: "Hello",
content: "World",
});
// GET /api/posts/:id/comments (with params + query)
const comments = await getPostsIdComments(
{ id: "123" }, // params
{ limit: 10, offset: 0 } // query
);
`
Generated services features:
- ā
Only necessary parameters are included (params, body, query only appear if defined)options?: RequestInit
- ā
Unique function names automatically generated
- ā
Complete types for autocomplete and type-checking (TypeScript projects)
- ā
Last parameter is always for fetch customization
Note: For JavaScript projects, Vitek generates api.services.js without TypeScript types. For TypeScript projects, it generates both api.types.ts and api.services.ts.
---
`typescript
import { vitek } from "vitek-plugin";
export default defineConfig({
plugins: [
vitek({
apiDir: "src/api", // API directory (default: 'src/api')
apiBasePath: "/api", // API base path (default: '/api')
enableValidation: false, // Enable automatic validation (default: false)
logging: {
level: "info", // Log level: 'debug' | 'info' | 'warn' | 'error'
enableRequestLogging: false, // Log all requests/responses (default: false)
enableRouteLogging: true, // Log route matches (default: true)
},
}),
],
});
`
| Option | Type | Default | Description |
| ------------------------------ | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------ |
| apiDir | string | 'src/api' | Directory where API route files are located |apiBasePath
| | string | '/api' | Base path for all API routes |enableValidation
| | boolean | false | Enable automatic request validation (currently manual validation is available via helpers) |logging
| | object | undefined | Logging configuration |logging.level
| | 'debug' \| 'info' \| 'warn' \| 'error' | 'info' | Minimum log level to display |logging.enableRequestLogging
| | boolean | false | Log all HTTP requests with method, path, status code, and duration |logging.enableRouteLogging
| | boolean | true | Log route matches when requests are handled |
---
This repository includes three complete examples demonstrating different use cases:
Pure JavaScript, no frameworks
- Minimal example with pure JavaScript
- No TypeScript, no React
- Simple HTML page with fetch API
- Perfect for understanding the basics
When to use: Start here if you want to learn the fundamentals without any framework overhead.
JavaScript with React (no TypeScript)
- React application in JavaScript
- Uses generated services (without TypeScript types)
- Demonstrates Vitek integration with React
- Intermediate complexity
When to use: Perfect for React projects that prefer JavaScript over TypeScript.
Complete TypeScript with React
- Full-featured example with TypeScript and React
- Complete type-safety with generated types
- Hierarchical middlewares
- All HTTP methods and advanced features
- Most comprehensive example
When to use: Reference implementation showing all Vitek features with full type-safety.
---
Vitek follows a modular architecture:
- Core: Logic independent of Vite (reusable for other runtimes)
- Adapters: Integration with different runtimes (currently Vite)
- Plugin: Thin layer that registers the plugin in Vite
This allows the core to be used in other contexts (standalone Node.js, other bundlers, etc).
``
vitek-plugin/
āāā src/
ā āāā core/ # Runtime-agnostic core logic
ā ā āāā context/ # Context creation
ā ā āāā file-system/ # File scanning and watching
ā ā āāā middleware/ # Middleware composition
ā ā āāā normalize/ # Path normalization
ā ā āāā routing/ # Route parsing and matching
ā ā āāā types/ # Type generation
ā ā āāā validation/ # Request validation
ā āāā adapters/ # Runtime-specific adapters
ā ā āāā vite/ # Vite integration
ā āāā shared/ # Shared utilities
ā ā āāā errors.ts # Error classes
ā ā āāā response-helpers.ts # Response helper functions
ā āāā plugin.ts # Vite plugin entry point
---
1. Scan: The plugin scans src/api looking for route files and middlewares/api/*
2. Loading: Loads handlers and middlewares using Vite's module system
3. Type Generation: Analyzes files and automatically generates TypeScript types (for TS projects)
4. Service Generation: Creates typed helper functions for the frontend
5. Integration: Registers middleware in the Vite server to intercept requests
6. Hot Reload: Watches for file changes and automatically reloads
---
`bash`
npm run buildor
pnpm build
`bash`
npm run devor
pnpm dev
This runs TypeScript compiler in watch mode.
---
To contribute, make sure to follow the steps below:
1. Create a new branch:
`shell`
git checkout -b feat/your-new-feature
2. Make your changes, add unit tests (if applicable) and test with npm link
On vitek-plugin project:
`shell`
npm link
On your app/project:
`shell`
npm link vitek-plugin
This will create a symlink into your node_modules app, and you can test iteratively. You can check more about npm-link here
3. Before to push your changes to origin, open your pull request and fill all required fields.
1. Make sure to fill the Release section with what your pull request changes. This section is required to merge pull request.
4. Set a _required_ semver label according to your change:semver:patch
1. : used when you submit a fix to a bug, enhance performance, etc;semver:minor
2. : used when you submit a new component, new feature, etc;semver:major
3. : used when you submit some breaking change, etc;semver:prerelease
4. : used when you submit a prerelease (ex: 0.1.0-beta.1);semver:bypass
5. : used to update docs, or something that doesn't affect the build.
> Info: Once you have merged your pull request, with all required fields, GitHub Actions will be responsible to create a new build and publish.
---
This project is licensed under the MIT License - see the LICENSE file for details.
MIT License means:
- ā
Free to use for personal and commercial projects
- ā
Free to modify
- ā
Free to distribute
- ā
Free to use privately
- ā
No warranty or liability
---
This project is currently in beta/testing phase.
- Version: 0.1.0-beta`
- APIs may change in future releases
- Some features may be experimental
- Feedback and bug reports are welcome!
---
- Built with Vite
- Inspired by file-based routing patterns from Next.js and SvelteKit
- Type generation powered by TypeScript
---
- š Found a bug? Open an issue
- š” Have a suggestion? Open a discussion
- š Need help? Check the examples directory
---