Skip to content

vextjs/vext

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

136 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VextJS

VextJS is a high-performance Node.js framework for building maintainable backend services. It combines a convention-based project structure, file-system routing, typed services, plugins, middleware, validation, OpenAPI generation, route-level caching, and a CLI workflow that keeps projects productive from the first command.

Why VextJS

  • Convention-based structure for routes, services, middleware, plugins, config, locales, generated types, and preload scripts.
  • File-system routing with dynamic params, nested routes, validation, middleware, OpenAPI metadata, and response helpers.
  • Adapter support for Native Node.js, Hono, Fastify, Express, and Koa.
  • Automatic service injection through app.services.
  • Plugin lifecycle hooks with app extension support.
  • Built-in request context, request id, access logging, body limit, error handling, i18n, and OpenAPI endpoints.
  • Route-level response cache with LRU memory storage.
  • Hot development workflow with route hot swap, service/i18n reload, and cold restart only when required.
  • Type generation for service and plugin app extensions.
  • Process-level preload support for OpenTelemetry, APM, polyfills, and startup bridges.

Quick Start

npx vextjs create my-app
cd my-app
npm run dev

Open http://localhost:3000. The scaffold includes a root route and a health check so the project is runnable immediately.

Create a project with another adapter:

npx vextjs create my-app --adapter hono

Create a JavaScript project:

npx vextjs create my-app --js

Skip dependency installation:

npx vextjs create my-app --skip-install

Installation

Manual setup is also supported:

npm install vextjs

package.json:

{
  "name": "my-app",
  "type": "module",
  "scripts": {
    "dev": "vext dev",
    "build": "vext build",
    "start": "vext start"
  },
  "dependencies": {
    "vextjs": "^0.3.12"
  }
}

VextJS projects use ESM. Keep "type": "module" in application packages.

Project Structure

The scaffold creates the convention directories that the runtime knows how to scan:

my-app/
|-- preload/                    # Optional process-level preload scripts
|   `-- README.md
|-- src/
|   |-- config/
|   |   |-- default.ts          # Required base config
|   |   |-- development.ts      # Development override
|   |   |-- production.ts       # Production override
|   |   |-- local.example.ts    # Copy to local.ts for private local overrides
|   |   `-- bootstrap.example.ts # Copy to bootstrap.ts for startup providers
|   |-- routes/
|   |   `-- index.ts
|   |-- services/
|   |   `-- example.ts
|   |-- middlewares/
|   |   `-- README.md
|   |-- plugins/
|   |   `-- README.md
|   |-- locales/
|   |   `-- README.md
|   `-- types/
|       `-- generated/
|           `-- .gitkeep
|-- package.json
`-- tsconfig.json

JavaScript projects use .js files and do not create src/types/generated/.

local.example.ts and bootstrap.example.ts are examples, not active config files. Copy them when you need the feature:

cp src/config/local.example.ts src/config/local.ts
cp src/config/bootstrap.example.ts src/config/bootstrap.ts

src/config/local.ts and src/config/local.js are ignored by the generated .gitignore because they may reference private local infrastructure.

CLI

vext dev              # Development mode with hot reload
vext build            # Build TypeScript projects
vext start            # Start the production server
vext create <name>    # Create a new project
vext typegen          # Generate service and app extension types
vext stop             # Stop cluster workers
vext reload           # Rolling restart for cluster workers
vext status           # Inspect cluster status

vext create options:

vext create my-app
vext create my-app --js
vext create my-app --adapter hono
vext create my-app --adapter fastify
vext create my-app --adapter express
vext create my-app --adapter koa
vext create my-app --adapter native
vext create my-app --skip-install
vext create my-app --force

Configuration

Configuration is loaded and merged in this order:

framework defaults -> default -> NODE_ENV file -> local -> bootstrap provider patch -> CLI override

src/config/default.ts:

import type { VextUserConfig } from "vextjs";

const config: VextUserConfig = {
  port: 3000,
  adapter: "native",
  logger: {
    level: "info",
    pretty: true,
  },
  openapi: {
    enabled: true,
  },
};

export default config;

Environment files can return partial config:

// src/config/production.ts
import type { VextUserConfig } from "vextjs";

const config: Partial<VextUserConfig> = {
  port: 3001,
  logger: {
    level: "info",
    pretty: false,
  },
};

export default config;

Use src/config/local.ts for machine-specific overrides and keep it out of Git.

Startup Config Providers

Use src/config/bootstrap.ts when configuration must be fetched before the final app config is validated and frozen:

import { defineBootstrapConfig } from "vextjs";

export default defineBootstrapConfig({
  providers: [
    {
      name: "remote-config",
      async load({ env, signal }) {
        const response = await fetch(`https://config.example.com/${env}.json`, {
          signal,
        });
        return await response.json();
      },
    },
  ],
});

This is the right place for startup config centers and early infrastructure patches. Use preload/ instead for APM, OpenTelemetry, polyfills, or anything that must execute before application modules are imported.

Preload

VextJS supports two preload sources:

  • Application-level scripts in the project root preload/ directory.
  • Package-level scripts declared through package.json vext.preload.

Application preload example:

preload/
|-- 01-otel.ts
`-- 02-polyfill.mjs

Supported application preload files include .js, .mjs, .ts, and .mts. TypeScript preload files are compiled before injection. vext dev watches the root preload/ directory and performs a cold restart when preload files change.

Routes

Routes live in src/routes/ and are mapped from file paths to URL prefixes:

src/routes/index.ts          -> /
src/routes/users.ts          -> /users
src/routes/admin/index.ts    -> /admin
src/routes/admin/settings.ts -> /admin/settings
src/routes/users/[id].ts     -> /users/:id

Example:

import { defineRoutes } from "vextjs";

export default defineRoutes((app) => {
  app.get(
    "/",
    {
      docs: { summary: "Home" },
    },
    async (_req, res) => {
      const greeting = await app.services.example.greeting("Vext");
      res.json(greeting);
    },
  );

  app.get(
    "/health",
    {
      docs: { summary: "Health check" },
    },
    async (_req, res) => {
      res.json({ status: "ok", timestamp: Date.now() });
    },
  );
});

Validation

Route validation uses schema-dsl style declarations:

app.post(
  "/users",
  {
    validate: {
      body: {
        name: "string!",
        age: "number|min:0",
        email: "email!",
      },
    },
  },
  async (req, res) => {
    const body = req.valid("body");
    res.json({ created: true, user: body });
  },
);

Validation errors use HTTP 422 by default and can be localized through src/locales/.

Services

Services live in src/services/ and are injected into app.services by filename:

// src/services/example.ts
import type { VextApp } from "vextjs";

export default class ExampleService {
  constructor(private app: VextApp) {}

  async greeting(name: string) {
    this.app.logger.info("Generating greeting", { name });
    return { message: `Hello, ${name}! Welcome to VextJS.` };
  }
}

Use it from a route:

const result = await app.services.example.greeting("Vext");

Run type generation after changing services or app extensions:

npx vext typegen

Generated declarations are written to src/types/generated/.

Middleware

Middleware files live in src/middlewares/ and are referenced by name from route config or global configuration.

// src/middlewares/auth.ts
import { defineMiddleware } from "vextjs";

export default defineMiddleware(async (req, res, next) => {
  if (!req.headers.get("authorization")) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  return next();
});

Plugins

Plugins live in src/plugins/ and can register lifecycle hooks, resources, and app extensions:

import { definePlugin } from "vextjs";

export default definePlugin({
  name: "redis",
  async setup(app) {
    app.extend("redis", {
      async ping() {
        return "PONG";
      },
    });
  },
});

After adding app extensions, run vext typegen so TypeScript consumers see the new fields.

Adapters

The default adapter is Native Node.js:

const config = {
  adapter: "native",
};

Other adapters are available through package subpaths:

import { honoAdapter } from "vextjs/adapters/hono";

export default {
  adapter: honoAdapter(),
};

Install the matching peer dependency before using a non-native adapter:

npm install hono @hono/node-server
npm install fastify
npm install express
npm install koa @koa/router

Route Cache

Route cache is enabled at route level:

app.get(
  "/articles",
  {
    cache: {
      ttl: 60_000,
      key: "articles:list",
    },
  },
  async (_req, res) => {
    res.json(await app.services.article.list());
  },
);

The current runtime uses MemoryCacheStore, an in-process LRU store. Cached GET or HEAD responses are written after a successful handler response and served by generated route cache middleware on later requests. Cache keys can be static strings or request-based functions.

OpenAPI

Enable OpenAPI in config:

export default {
  openapi: {
    enabled: true,
    title: "My API",
    version: "1.0.0",
  },
};

Then visit:

  • http://localhost:3000/docs
  • http://localhost:3000/openapi.json

Route metadata is collected from docs, validation declarations, parameters, responses, and route registration data.

i18n

Put locale files in src/locales/:

// src/locales/en-US.ts
export default {
  validation: {
    required: "This field is required.",
  },
};

The runtime automatically loads locale files during bootstrap. In development, locale changes trigger the service/i18n reload path.

Development Hot Reload

vext dev chooses the smallest safe reload strategy:

Change type Strategy
Route files Hot route replacement
Service or locale files Service/i18n reload
Config, plugin, preload, env, or package files Cold restart

TypeScript projects are compiled into .vext/dev/ during development.

Build And Start

npm run build
npm start

vext build compiles TypeScript source and project-level preload files. vext start runs the production bootstrap path and can read compiled preload files from dist/preload/ when the root preload/ directory is not present.

Testing Utilities

VextJS exports testing helpers through vextjs/testing:

import { createTestApp } from "vextjs/testing";

Use the testing entry for integration tests that need the framework runtime without starting a real production process.

Documentation

Requirements

  • Node.js >=18.0.0
  • ESM application packages

License

MIT

About

VextJS is a high‑performance Node.js framework that integrates scaffolding, modular architecture, and a plugin‑based runtime, enabling teams to build maintainable and scalable backend systems with exceptional development efficiency.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors