No reviews yetBe the first to leave a review for ZinTrust
Maker
📌
I built ZinTrust because I was tired of pulling in a dozen heavy dependencies just to stand up a clean, fast API. Most Node frameworks either lock you into a bloated ecosystem or leave you wiring everything together yourself.
ZinTrust is a lightweight TypeScript framework for building high-performance APIs and microservices with clean architecture baked in. By bypassing heavy external web frameworks (no Express or Fastify), it features a highly self-contained core that runs seamlessly across Node.js and Cloudflare Workers.
What makes it different:
Zero web-framework dependencies — built directly on top of native runtime APIs for smaller attack surfaces and faster cold starts.
First-class TypeScript — written entirely in TS, not bolted on.
Clean architecture by default — built-in dependency injection, ORM, and request kernels decouple your business logic from the infrastructure.
Write once, run anywhere — runs natively on both Node.js and Cloudflare Workers using the same application code.
It’s open source, and I’m building it in the open. Would love feedback from anyone building APIs or microservices — what’s your current framework, and what makes you want to switch (or stay)?
Happy to answer anything about the architecture or our lightweight, framework-free approach!
Report
How does ZinTrust handle middleware composition across multiple Workers, especially when some run on the edge and others on Node? Curious if there's a shared pattern or if you end up writing it twice.
Report
Maker
@zmragetrktzpl Yes — the same middleware source code runs unchanged on edge (Cloudflare Workers/serverless) and Node. There is one shared, runtime-agnostic middleware interface and composition model. The runtime differences are handled under the hood by adapters. You do not write two versions.
The middleware code and composition are fully portable. However, some middlewares need to persist state across requests. On Node, you can sometimes use in-memory storage. On edge runtimes (stateless Workers), every request may land on a different isolate, so you must configure a shared backend.
ZinTrust provides driver options so you can keep the same middleware registration while swapping the storage:
Rate Limiting (rateLimit, authRateLimit, etc.):
Supports memory | redis | kv | db
Set RATE_LIMIT_STORE=kv (or redis via the proxy) for edge.
Configured globally in config/middleware.ts or per middleware instance.
JWT Auth + Sessions/Revocation:
JwtAuthMiddleware can validate against JwtSessions.isActive().
Configure with JWT_SESSION_DRIVER=kv or JWT_REVOCATION_DRIVER=redis.
Use this when you need to revoke a token or "logout all devices". Pure stateless JWT (no driver) works without any shared store.
Traditional Sessions (SessionMiddleware):
Currently uses an in-memory Map by default.
For durable sessions across edge Workers, most teams switch to stateless JWT (no server-side session storage needed) or implement a Cache-backed custom store.
CSRF protection often rides on top of sessions, so the same guidance applies.
Other stateful middleware (custom auth caches, feature flags tied to user, etc.):
Use Cache.store('redis'), Cache.store('kv'), or direct DB/KV access inside your middleware.
Example configuration for edge:
// config/middleware.ts
export default {
// Rate limiting shared across Workers
// RATE_LIMIT_STORE=kv (or redis)
fillRateLimit: { windowMs: 60000, maxRequests: 10 },
// JWT revocation using KV or Redis
// JWT_SESSION_DRIVER=kv
route: {
jwt: JwtAuthMiddleware.create(),
},
} as const;
The middleware functions themselves stay the same. Only the driver (chosen via env or options) changes depending on whether you are on Node or edge.
Report
zero deps is huge, actually deployed a small endpoint to cloudflare workers in like 10 minutes. routing feels snappy and the structure keeps things readable as it grows.
Report
Maker
@zcanurasltuzb Thanks, Özcan. I'm really glad to hear the Cloudflare Workers deployment went smoothly, and the routing felt snappy — that’s exactly what we were going for with the zero-deps approach. If you run into any issues as you scale it up, feel free to reach out anytime. Happy to help!
Report
how does it handle database integrations out of the box, or do i need to bring my own client setup?
ZinTrust gives you multiple supported options you can pick. It is not locked in. You configure the driver via DB_CONNECTION + config/database.ts. Install only the adapter package for the DB you choose (via zin add or npm i).
Supported drivers (pick any)
Driver
Adapter package (if needed)
Client used
Notes / When to use
mysql
@zintrust/db-mysql
mysql2
Default in examples. Full featured.
postgresql
@zintrust/db-postgres
pg
Full featured.
sqlite
@zintrust/db-sqlite (optional)
better-sqlite3
Built-in adapter; good for local/dev.
sqlserver
@zintrust/db-sqlserver
(mssql via setup)
Supported via plugin/adapter.
d1 / d1-remote
@zintrust/db-d1
Cloudflare D1 binding
Native CF, no TCP.
aurora-data-api
@zintrust/client-rds-data
AWS RDS Data API
For Aurora Serverless (set DB_CONNECTION=aurora-data-api).
Run zin add db:mysql (or db:postgres, a:mysql, etc.) — this installs the adapter and registers the driver automatically.
The adapter packages are separate, so you only pull mysql2 or pg for the DB you actually use.
Core already includes mysql2 + pg at the framework level for proxy runtimes and convenience.
Configuration (multi-DB supported, not locked)
// config/database.ts
export default {
default: Env.get('DB_CONNECTION', 'mysql'),
connections: {
mysql: { driver: 'mysql' as const, host: ..., /* pooling, readHosts, ssl... */ },
postgresql: { driver: 'postgresql' as const, ... },
// add more named connections freely
},
};
Use Model.db('postgresql') or useDatabase(..., 'name') to target a specific connection. All standard methods (query builder, transactions, migrations, seeding, relations) work against any registered connection.
Serverless runtimes cannot open raw TCP sockets by default.
Options (both supported out of the box):
Cloudflare sockets (direct, experimental): ENABLE_CLOUDFLARE_SOCKETS=true + the db adapter package. The adapter packages detect Workers runtime and use cloudflare:sockets.
Included HTTP proxy (recommended, stable):
USE_MYSQL_PROXY=true
MYSQL_PROXY_URL=http://... (same pattern for POSTGRES_PROXY_URL, SQLSERVER_PROXY_URL)
You can run the proxy (ships with the CLI and runtime) using any of these methods:
import { DatabaseAdapterRegistry } from '@zintrust/core/database';
DatabaseAdapterRegistry.register('mycustom', (cfg) => new MyAdapter(cfg));
Use any connection name + your driver in config.
You can still use Models, QueryBuilder, migrations, etc. against it.
Or bypass entirely and talk to your DB client directly for special cases.
What the ORM / framework uses
All paths go through the registered adapter for the connection:
Models (User.create, User.where().get())
QueryBuilder
Database.transaction(...)
Raw queries (when USE_RAW_QRY=true)
Migrations + seeders
Read replicas (readHosts), pooling config, SSL, etc.
Same application code works on Node, Cloudflare Worker, Docker, AWS Lambda, or serverless — only the driver + env + (for edge) proxy changes.
Summary of choices (you pick)
Direct client on Node/Docker/VPS: just pick driver + install adapter package.
Edge (Workers): D1 (zero proxy) or sockets or the built-in proxy (it provides the proxy command + signing).
Bring your own: inject module or register custom adapter.
Multiple DBs of mixed types: fully supported.
No vendor lock-in on the DB side. The proxies are optional tooling I include so you don't have to build the HTTP-to-TCP bridge yourself when targeting Workers.
How does ZinTrust handle middleware composition across multiple Workers, especially when some run on the edge and others on Node? Curious if there's a shared pattern or if you end up writing it twice.
@zmragetrktzpl
Yes — the same middleware source code runs unchanged on edge (Cloudflare Workers/serverless) and Node. There is one shared, runtime-agnostic middleware interface and composition model. The runtime differences are handled under the hood by adapters. You do not write two versions.
The middleware code and composition are fully portable. However, some middlewares need to persist state across requests. On Node, you can sometimes use in-memory storage. On edge runtimes (stateless Workers), every request may land on a different isolate, so you must configure a shared backend.
ZinTrust provides driver options so you can keep the same middleware registration while swapping the storage:
Rate Limiting (rateLimit, authRateLimit, etc.):
Supports memory | redis | kv | db
Set RATE_LIMIT_STORE=kv (or redis via the proxy) for edge.
Configured globally in config/middleware.ts or per middleware instance.
JWT Auth + Sessions/Revocation:
JwtAuthMiddleware can validate against JwtSessions.isActive().
Drivers: database | redis | kv | kv-remote | memory
Configure with JWT_SESSION_DRIVER=kv or JWT_REVOCATION_DRIVER=redis.
Use this when you need to revoke a token or "logout all devices". Pure stateless JWT (no driver) works without any shared store.
Traditional Sessions (SessionMiddleware):
Currently uses an in-memory Map by default.
For durable sessions across edge Workers, most teams switch to stateless JWT (no server-side session storage needed) or implement a Cache-backed custom store.
CSRF protection often rides on top of sessions, so the same guidance applies.
Other stateful middleware (custom auth caches, feature flags tied to user, etc.):
Use Cache.store('redis'), Cache.store('kv'), or direct DB/KV access inside your middleware.
Example configuration for edge:
The middleware functions themselves stay the same. Only the driver (chosen via env or options) changes depending on whether you are on Node or edge.
zero deps is huge, actually deployed a small endpoint to cloudflare workers in like 10 minutes. routing feels snappy and the structure keeps things readable as it grows.
@zcanurasltuzb
Thanks, Özcan. I'm really glad to hear the Cloudflare Workers deployment went smoothly, and the routing felt snappy — that’s exactly what we were going for with the zero-deps approach. If you run into any issues as you scale it up, feel free to reach out anytime. Happy to help!
how does it handle database integrations out of the box, or do i need to bring my own client setup?
@nimeta4h7
ZinTrust gives you multiple supported options you can pick. It is not locked in. You configure the driver via DB_CONNECTION + config/database.ts. Install only the adapter package for the DB you choose (via zin add or npm i).
Supported drivers (pick any)
Driver
Adapter package (if needed)
Client used
Notes / When to use
mysql
@zintrust/db-mysql
mysql2
Default in examples. Full featured.
postgresql
@zintrust/db-postgres
pg
Full featured.
sqlite
@zintrust/db-sqlite (optional)
better-sqlite3
Built-in adapter; good for local/dev.
sqlserver
@zintrust/db-sqlserver
(mssql via setup)
Supported via plugin/adapter.
d1 / d1-remote
@zintrust/db-d1
Cloudflare D1 binding
Native CF, no TCP.
aurora-data-api
@zintrust/client-rds-data
AWS RDS Data API
For Aurora Serverless (set DB_CONNECTION=aurora-data-api).
Run zin add db:mysql (or db:postgres, a:mysql, etc.) — this installs the adapter and registers the driver automatically.
The adapter packages are separate, so you only pull mysql2 or pg for the DB you actually use.
Core already includes mysql2 + pg at the framework level for proxy runtimes and convenience.
Configuration (multi-DB supported, not locked)
Use Model.db('postgresql') or useDatabase(..., 'name') to target a specific connection. All standard methods (query builder, transactions, migrations, seeding, relations) work against any registered connection.
Serverless + TCP databases (mysql/postgres/sqlserver)
Serverless runtimes cannot open raw TCP sockets by default.
Options (both supported out of the box):
Cloudflare sockets (direct, experimental): ENABLE_CLOUDFLARE_SOCKETS=true + the db adapter package. The adapter packages detect Workers runtime and use cloudflare:sockets.
Included HTTP proxy (recommended, stable):
USE_MYSQL_PROXY=true
MYSQL_PROXY_URL=http://... (same pattern for POSTGRES_PROXY_URL, SQLSERVER_PROXY_URL)
You can run the proxy (ships with the CLI and runtime) using any of these methods:
zin proxy:mysql (or --watch)
docker compose -f docker-compose.proxy.yml up (includes mysql, postgres, sqlserver proxies + gateway)
Cloudflare Containers proxy gateway (zin init:containers-proxy)
Proxy runs in normal Node (has full TCP + real mysql2/pg), Worker talks HTTP to it.
Optional: request signing (MYSQL_PROXY_REQUIRE_SIGNING), custom headers, statement registry mode.
D1 does not need a proxy.
Bring your own client/custom setup
Yes, fully supported — you are not locked to the provided adapters.
Inject your own mysql2: before boot, set globalThis.__zintrustMysqlModule = yourModule (the adapter checks for it).
Custom driver/adapter: implement IDatabaseAdapter (methods: connect, disconnect, query, queryOne, ping, transaction, rawQuery, ensureMigrationsTable, getType, isConnected, getPlaceholder) then:
Use any connection name + your driver in config.
You can still use Models, QueryBuilder, migrations, etc. against it.
Or bypass entirely and talk to your DB client directly for special cases.
What the ORM / framework uses
All paths go through the registered adapter for the connection:
Models (User.create, User.where().get())
QueryBuilder
Database.transaction(...)
Raw queries (when USE_RAW_QRY=true)
Migrations + seeders
Read replicas (readHosts), pooling config, SSL, etc.
Same application code works on Node, Cloudflare Worker, Docker, AWS Lambda, or serverless — only the driver + env + (for edge) proxy changes.
Summary of choices (you pick)
Direct client on Node/Docker/VPS: just pick driver + install adapter package.
Edge (Workers): D1 (zero proxy) or sockets or the built-in proxy (it provides the proxy command + signing).
Bring your own: inject module or register custom adapter.
Multiple DBs of mixed types: fully supported.
No vendor lock-in on the DB side. The proxies are optional tooling I include so you don't have to build the HTTP-to-TCP bridge yourself when targeting Workers.