@xitkov/core
v2.7.1
Published
TypeScript backend meta-framework on Express. Convention-over-configuration REST APIs, dual database dialect (MongoDB + Postgres), microservice HTTP clients, hooks, auth, cache, queue, sockets — one dependency.
Readme
Xitkov
TypeScript backend meta-framework on Express. Convention-over-configuration REST APIs, dual database dialect (MongoDB + Postgres), microservice HTTP clients, hooks, auth, cache, queue, sockets — one dependency.
Install
npm install xitkovQuickstart
Project layout:
src/
├─ index.ts
├─ services/
│ └─ users/
│ ├─ service.ts # extends XitkovService
│ ├─ model.ts # mongoose or sequelize model
│ ├─ validator.ts # optional Joi schemas
│ └─ hooks.ts # optional per-method hooks
└─ micro-services/
└─ billing.ts # optional external service clientNested services
A service folder inside another is served beneath it. Position is the only declaration needed:
services/
├─ skills/
│ ├─ service.ts # /skills
│ └─ versions/
│ └─ service.ts # /skills/:parentId/versions
└─ conversations/
├─ service.ts # /conversations
└─ messages/
└─ service.ts # /conversations/:parentId/messagesRead the parent from req.params.parentId, and address the service by its
path: Services.get('skills.versions').
A folder with no service module is a grouping folder, not a service; the
services inside it are still found.
src/index.ts:
import express from 'express';
import { Xitkov } from 'xitkov';
const app = express();
Xitkov.create({
app,
database: { dialect: 'mongodb', instance: mongooseConnection },
auth: { required: true, baseURL: 'https://auth.example.com' },
caching: { redisUrl: 'redis://localhost:6379', keyPrefix: 'app:' },
socket: { enable: true, socketCorsOrigin: '*' },
});src/services/users/service.ts:
import { XitkovService } from 'xitkov';
import Model from './model';
export default class UserService extends XitkovService<User> {
constructor() {
super({ Model, dialect: 'mongodb' });
}
}Boots with REST routes:
| Method | Path | Handler |
|--------|------|---------|
| GET | /users | find |
| GET | /users/:id | get |
| POST | /users | create |
| PATCH | /users/:id | patch |
| PUT | /users/:id | update |
| DELETE | /users/:id | remove |
Core APIs
Xitkov
Bootstrapper. Registers services, wires cache/socket/logger, listens on PORT.
new Xitkov(config) // fire-and-forget, use `xitkov.ready`
await Xitkov.create(config) // async factory, resolves after bootConfig
| Field | Type | Purpose |
|-------|------|---------|
| app | Application | Existing Express app |
| database.dialect | 'mongodb' \| 'postgres' | Backing store |
| database.instance | any | Sequelize instance (postgres) |
| auth.required | boolean | Enforce JWT verify on every route |
| auth.baseURL | string | Verify endpoint (POST /verify) |
| caching.redisUrl | string | Redis URL for Cache |
| caching.keyPrefix | string | Key namespace |
| socket.enable | boolean | Boot socket.io |
| socket.redis | { url?, redisOptions? } | Redis pub/sub adapter |
| socket.socketCorsOrigin | string | Socket CORS |
| logger.express | LoggerOptions | express-winston passthrough |
| express.limit | string | Body-parser limit (default 50mb) |
XitkovService<T>
Base class every service extends. Wraps dialect-specific implementation.
Methods: find, get, create, patch, update, remove — plus underscored bypass variants _find … _remove (skip hooks + validators when called internally).
Instance flags:
pagination?: boolean— postgresfindAndCountAllfiltering?: boolean— toggleaddQuerySupporttranslation (default on)auth?: boolean— setfalseto disable auth on this service
MicroService
Typed Axios client with the same CRUD shape. Auto-created for every entry in /micro-services.
export default class BillingService {
name = 'billing'
baseURL = process.env.BILLING_URL!
services = ['invoices', 'customers']
}
// then anywhere:
const invoice = await MicroServices.get('billing', 'invoices').get('inv_1');Per-call options:
await MicroServices.get('billing', 'invoices').get(id, undefined, {
axiosConfig: { timeout: 5000 },
exception: { handle: true, defaultValue: null },
});Hooks
Drop hooks.ts next to a service; export default class with method arrays:
import { setBusinessId, loggedIn } from 'xitkov';
export default class UserHooks {
all = [loggedIn]
find = [setBusinessId]
create = [setBusinessId]
}Built-in hooks: disallow, externalDisallow, loggedIn, setBusinessId, addQuerySupport.
Errors
All extend Error. Throw them; framework maps to HTTP:
import { BadRequest, NotFound, Unauthorized } from 'xitkov';
throw new NotFound('User not found');Classes: BadRequest (400), NoContent (204), Unauthorized (401), PaymentRequired (402), Forbidden (403), NotFound (404), InternalServer (500).
Cache
Redis wrapper (ioredis).
import { Cache } from 'xitkov';
await Cache.set('key', value, 300); // TTL in seconds
const value = await Cache.get('key');
await Cache.delete('key');Queue
Bull wrapper with env-scoped names (name_dev, name_staging, bare in prod).
import { Queue } from 'xitkov';
const emails = new Queue<{ to: string }>('emails', process.env.REDIS_URL!);
await emails.add({ to: '[email protected]' });
emails.consume(async job => sendEmail(job.data));Socket
Socket.io helper. Redis adapter attached when socket.redis provided.
import { Socket } from 'xitkov';
Socket.emit('event', payload);
Socket.client(socket => {
socket.on('ping', () => socket.emit('pong'));
});Logger
Winston-backed static class. Local dev logs to console; prod to structured JSON.
import { Logger } from 'xitkov';
Logger.info('boot', { port });
Logger.error('failed', err);Events
import { XitkovEvents, EVENTS } from 'xitkov';
XitkovEvents.on(EVENTS.SERVICES_REGISTERED, () => {});
XitkovEvents.on(EVENTS.SERVER_STARTED, port => {});Query operators (postgres)
addQuerySupport translates $op shorthand → Sequelize Op:
GET /users?age[$gte]=18&status[$in]=["active","pending"]Reserved: $limit, $offset for pagination.
Environment variables
| Var | Default | Purpose |
|-----|---------|---------|
| PORT | 3000 | HTTP listen port |
| NODE_ENV | — | Set local to disable Winston, use console |
| ENVIRONMENT | dev | Bull queue name suffix (prod → bare) |
| INTERNAL_TOKEN | internal_token | Shared secret for microservice-to-microservice calls (bypasses auth) |
Type augmentation
Register your services for typed access:
declare module 'xitkov' {
interface ServiceTypes {
users: UserService
}
interface MicroServiceNames {
billing: {
invoices: MicroService
customers: MicroService
}
}
}License
MIT
