@sentzunhat/zacatl
v0.0.64
Published
A modular, high-performance TypeScript microservice framework for Node.js, featuring layered architecture, dependency injection, and robust validation for building scalable APIs and distributed systems.
Maintainers
Keywords
Readme
Zacatl Framework
Package
Quality
Stack
CIis the orchestrator — CVE scanning, peer-dependency install checks, the full test suite, and an 8-example Docker smoke matrix, gating every merge tomain.CVE scanruns that same audit again on its own weekly schedule so its badge stays live independent ofCI.Releasereflects the actual tag → npm publish → GitHub Release pipeline, only fired by real releases. There's no separate "Publish dry-run" badge: that check only runs as part ofCI(gated on a pending version bump), and giving it its own trigger would mean it either fails on ordinary non-release pushes or needs to duplicate that gate — not worth it for one more badge. See docs/guidelines/ci-cd-workflow.md for exactly what runs where and why.
Build consistent TypeScript services without giving up the frameworks you already use.
Zacatl gives TypeScript teams a repeatable application → domain → infrastructure → platform structure, built-in dependency injection, type-safe validation, and structured errors. You keep using Fastify, Express, Mongoose, or Sequelize; Zacatl supplies the boundaries that keep those choices from leaking through the whole application.
It is aimed first at teams building modular APIs and backend services that want consistent project structure, replaceable adapters, and earlier feedback from tests and CI/CD.
Table of Contents
- Why Zacatl
- See It in a Real Service
- Quick Start
- ORM Adapters
- Architecture
- Platform Support
- Examples & Screenshots
- Public API Modules
- Documentation
- Testing
- Requirements
- Contributing
- License
- Acknowledgments
✨ Why Zacatl
Zacatl is useful when a service needs to stay understandable as it grows, or when several services need to follow the same architectural rules without sharing one large application.
- Keep business logic portable: domain services depend on repository ports, while database and platform adapters remain replaceable.
- Make boundaries visible: routes, validation, services, repositories, and startup wiring have clear homes and test seams.
- Catch drift earlier: typed contracts, focused tests, dependency checks, and CI-friendly verification make consistency part of the workflow.
| Capability | Detail |
| ---------------------------- | ------------------------------------------------------------------ |
| 🏗️ Layered Architecture | Strict Application → Domain → Infrastructure → Platform layers |
| 💉 Dependency Injection | Built-in DI container via tsyringe |
| ✅ Type-Safe Validation | Zod schema support; Yup and optional validation planned |
| 🛡️ Structured Errors | 8 custom error types with correlation IDs |
| 🗄️ Pluggable ORM Adapters | Sequelize, Mongoose, built-in SQLite via node:sqlite, or custom |
| 🌐 Server Framework Choice | Fastify or Express — same domain logic, swap the adapter |
| 🌍 Internationalization | Pluggable i18n with filesystem/memory adapters |
| 📝 Production Observability | Structured logging and error tracking |
| 🧪 Tested | Vitest suite — counts and coverage in the badges; see Testing |
🧭 See It in a Real Service
The fastify-sqlite-react example is a complete greeting CRUD service, not just a bootstrap snippet:
HTTP request
→ validated route handler
→ GreetingService
→ GreetingRepository port
→ SQLite adapter
→ typed responseIt includes create, list, update, and delete flows with validation, domain logic, repository wiring, persistence, and a React frontend. Run it locally with:
cd examples/fastify-sqlite-react
docker compose up -d
curl http://localhost:8081/api/greetingsThe same greeting flow is implemented across Fastify/Express, SQLite/PostgreSQL/MongoDB, and React/Svelte in the examples matrix, making the adapter boundaries visible instead of theoretical.
🚀 Quick Start
npm install @sentzunhat/zacatlCore server dependencies are installed with Zacatl. ORM/database adapters use optional peer dependencies (
mongoose,mongodb,sequelize,sqlite3,pg) that you install based on what your service uses. SQL projects that use Zacatl's Sequelize adapter should installsequelizeplus the dialect driver, for examplepgorsqlite3.Audit note for 0.0.57+: non-SQL consumers should not install
sequelize, and should not inherit Sequelize's nested audit surface through Zacatl core. SQL consumers that explicitly installsequelizeown that SQL dependency tree. Apps that only need local SQLite can use Zacatl'snode:sqlitepath on Node 26 without externalsqlite3or Sequelize.
import Fastify from 'fastify';
import { Service, ServiceType, ServerType, ServerVendor } from '@sentzunhat/zacatl/service';
const fastify = Fastify();
const service = new Service({
type: ServiceType.SERVER,
layers: {
application: { entryPoints: { rest: { hooks: [], routes: [] } } },
domain: { services: [] },
infrastructure: { repositories: [] },
},
platforms: {
server: {
name: 'hello-service',
server: { type: ServerType.SERVER, vendor: ServerVendor.FASTIFY, instance: fastify },
},
},
});
await service.start({ port: 3000 });See examples/ for eight production-ready starters spanning Express/Fastify × SQLite/PostgreSQL/MongoDB × React/Svelte.
🗄️ ORM Adapters
Zacatl doesn't reimplement your database driver — it registers the real thing (Mongoose, Sequelize, or Node's built-in node:sqlite) on the service's platforms.server.databases list, and gives repositories a consistent shape regardless of which one you picked.
Mongoose (excerpt from examples/fastify-mongodb-react):
import mongoose from 'mongoose';
import { DatabaseVendor } from '@sentzunhat/zacatl/service/platforms/server/database/port';
const serviceConfig = {
// ...
platforms: {
server: {
// ...
databases: [
{
vendor: DatabaseVendor.MONGOOSE,
instance: mongoose,
connection: { url: process.env.MONGO_URI ?? 'mongodb://localhost:27017/myapp' },
indexes: { bootMode: 'create' }, // 'off' | 'create' | 'sync' — safe by default in prod
},
],
},
},
};node:sqlite — zero extra dependencies, no sequelize or sqlite3 needed on Node 26+:
import { DatabaseVendor } from '@sentzunhat/zacatl/service/platforms/server/database/port';
const databases = [{ vendor: DatabaseVendor.NODE_SQLITE, connection: { url: './data/app.db' } }];Sequelize (Postgres, MySQL, SQLite via the traditional driver) follows the same databases[] shape with DatabaseVendor.SEQUELIZE — see docs/third-party/README.md for the full adapter matrix, and docs/migration/sequelize-sqlite-to-nodesqlite.md if you're moving a small app off Sequelize SQLite.
🏗️ Architecture
flowchart TB
subgraph App["Application Layer"]
direction LR
A1[HTTP Handlers]
A2[Validation]
A3[Routes / Hooks]
end
subgraph Domain["Domain Layer"]
direction LR
D1[Business Logic]
D2[Services]
D3[Ports]
end
subgraph Infra["Infrastructure Layer"]
direction LR
I1[Repositories]
I2[Database Adapters]
end
subgraph Platform["Platform Layer"]
direction LR
P1[DI Container]
P2[Service Startup]
P3[Server Vendor: Fastify / Express]
end
App --> Domain --> Infra --> PlatformDependencies flow strictly inward — Application depends on Domain, Domain depends on Infrastructure ports (not concrete adapters), and Platform wires the concrete Fastify/Express/Mongoose/Sequelize instances in at startup. The public API surface is stable; internal modules may change between minor versions.
🎯 Platform Support
| Platform | Status | | ------------- | -------------------- | | Server (HTTP) | Stable | | CLI | Planned (stub only) | | Desktop | Planned (stub only) |
For non-HTTP scripts and workers today, use the standalone DI helpers from
@sentzunhat/zacatl/dependency-injectiondirectly.
📸 Examples & Screenshots
Every example implements the same greeting CRUD flow behind a different framework/database/frontend combination, so you can compare them directly. Screenshots (and the walkthrough below) are captured automatically via Playwright against the real, dockerized app — not mockups.
Fastify + MongoDB + React, full create → update → delete cycle:

| Example | Server | Database | Frontend | Preview |
| ---------------------------------------------------------------- | ------- | ---------- | -------- | -------------------------------------------------------------------------------- |
| fastify-sqlite-react | Fastify | SQLite | React |
|
| fastify-mongodb-react | Fastify | MongoDB | React |
|
| fastify-postgres-react | Fastify | PostgreSQL | React |
|
| fastify-sqlite-svelte | Fastify | SQLite | Svelte |
|
The full gallery (all 8 examples × 4 states: initial, after-create, after-update, after-delete) lives in examples/README.md.
Run one locally with Docker
cd examples/fastify-sqlite-react
docker compose up -d
curl http://localhost:8081/api/greetingsEach example builds one Docker image containing both the compiled backend and the compiled frontend static files — there's no separate frontend container to wire up. MongoDB/PostgreSQL examples bring their own database sidecar via docker compose; SQLite examples need nothing extra. Full architecture, port table, and image-pinning guidance: examples/DOCKER.md.
📦 Public API Modules
| Module | Import path |
| ----------------------- | -------------------------------------------- |
| Service | @sentzunhat/zacatl/service |
| Configuration | @sentzunhat/zacatl/configuration |
| Dependency Injection | @sentzunhat/zacatl/dependency-injection |
| Errors | @sentzunhat/zacatl/error |
| Logs | @sentzunhat/zacatl/logs |
| Localization | @sentzunhat/zacatl/localization |
| Utils | @sentzunhat/zacatl/utils |
| Third-party re-exports | @sentzunhat/zacatl/third-party/* |
📖 Documentation
Full docs live in docs/. New contributors start with docs/start-here.md.
| Topic | Link | | ------------------------ | ----------------------------------------------------------------------------------------------------- | | Architecture Overview | docs/guidelines/framework-overview.md | | Service Module | docs/service/README.md | | Dependency Injection | docs/dependency-injection/README.md | | Configuration | docs/configuration/README.md | | Errors | docs/error/README.md | | Logs | docs/logs/README.md | | Localization | docs/localization/README.md | | Third-Party + ORM | docs/third-party/README.md | | ESLint | docs/eslint/README.md | | Utils | docs/utils/README.md | | CI/CD Pipeline | docs/guidelines/ci-cd-workflow.md | | Release Notes | docs/changelog.md | | Migration Guides | docs/migration/ |
🧪 Testing
npm test # Run all tests
npm run test:coverage # Coverage report691 tests across 83 files, 92.10% line coverage. CI runs the full suite plus type-check, lint, and an 8-example Docker smoke matrix before any release — see docs/guidelines/ci-cd-workflow.md for exactly what gates what.
📋 Requirements
- Node.js: 26.0.0+
- npm: 11.0.0+
- TypeScript: 6.0+
🤝 Contributing
- Open an issue describing the change.
- Branch off it:
git checkout -b issue-<number>/type/description - Add tests, update docs, commit with Conventional Commits.
See CONTRIBUTING.md for the full guide.
📄 License
Apache License 2.0 © 2026 Zacatl Contributors
Zacatl is permissively licensed. Please don't use it to harm people.
🤖 Acknowledgments
This framework was built with assistance from AI coding models and agents — including Claude (Anthropic), OpenAI models, GitHub Copilot, and open-source tooling like Cursor and Continue — alongside plenty of human review and decision-making. Zacatl's own design leans into that collaboration: its docs and structure are intentionally written to be navigable by both people and AI agents. Thank you to the teams building these tools.
