Documentation Index
Fetch the complete documentation index at: https://docs-kfhye.zochil.dev/llms.txt
Use this file to discover all available pages before exploring further.
Module Structure
A module is the basic building block of the Zochil API. Each module represents a specific business domain and contains two essential files: routes.ts and service.ts.
Module Pattern
domain_module/
├── routes.ts # Route definitions and Express router
└── service.ts # Business logic and data access
Routes (routes.ts)
The routes.ts file creates and exports an Express Router. It handles HTTP route definitions, request validation, and middleware integration.
Example Route File
import { Router } from "express";
import { body as checkBody } from "express-validator";
import SomeService from "./service";
import { DBConnection } from "core/types";
export default (db: DBConnection) => {
const routes: any = Router();
const service = new SomeService(db);
routes.get(
"/list",
service.handleOk(async (req) => await service.getAll(req.query))
);
routes.post(
"/create",
[checkBody("name").notEmpty()],
service.handleOk(async (req) => await service.createRecord(req.body))
);
return routes;
};
Route Responsibilities
- Express Router: Create and configure HTTP routes
- Validation: Input validation with express-validator
- Authentication: Apply authentication middleware
- Response Handling: Use
service.handleOk() for consistent responses
- Dependency Injection: Receive
DBConnection from parent router
Services (service.ts)
The service.ts file contains the business logic and data access layer. All service classes extend the base APIService class.
Example Service File
import APIService from "core/base/service";
import { DBConnection, ID } from "core/types";
export default class SomeService extends APIService {
constructor(db: DBConnection) {
super(db, "table_name");
}
async getAll(filters = {}) {
return await this.findForList(filters);
}
async createRecord(data: any) {
return await this.create(data);
}
}
Service Responsibilities
- Business Logic: Core domain logic and data processing
- Database Access: Query database using Knex.js patterns
- Multi-tenancy: Automatic filtering by
shop_id/merchant_id
- Error Handling: Use
CustomError for structured exceptions
- Event Publishing: Publish domain events via
this.publishEvent()
Base Classes
APIService Base Class
Located at ./api/@core/base/service.ts, provides common functionality:
import APIService from "core/base/service";
export default class ModuleService extends APIService {
constructor(db: DBConnection) {
super(db, "module_table_name");
}
// Inherited methods available:
// - findForList(filters, pagination)
// - findOneOrThrow(id)
// - create(data)
// - update(id, data)
// - publishEvent(eventName, data)
}
Merchant Service Composition
For merchant-related functionality, use the MerchantService as a composed property:
import APIService from "core/base/service";
import MerchantService from "core/merchants/service";
import { DBConnection } from "core/types";
export default class ModuleWithMerchant extends APIService {
private _merchantService: MerchantService;
constructor(db: DBConnection) {
super(db, "module_table_name");
this._merchantService = new MerchantService(db, "shops");
}
async getMerchantData(merchantId: string) {
return await this._merchantService.findOneOrThrow(merchantId);
}
}
Database Connection
All modules receive a DBConnection interface containing:
export type DBConnection = {
db: Knex; // PostgreSQL connection
kv: RedisClient; // Redis connection
paymentDB?: Knex; // Optional separate payment DB
};
Database Access Patterns
- Knex Queries: Use
this.connector.db(tableName) for database access
- Multi-tenancy: Always filter by
shop_id or merchant_id
- Transactions: Use Knex transactions for complex operations
- Connection Pooling: Automatic connection management
Authentication & Middleware
Request Context
Authentication middlewares populate request objects with user context:
// Available on authenticated requests
req.currentUser; // User data (excluding sensitive fields)
req.merchant_id; // Merchant ID from headers
req.device_id; // Device ID from headers
Authentication Middleware
import { authenticateUser, authenticateAdmin } from "core/auth/middlewares";
// In routes.ts
routes.use("*", authenticateUser(db)); // For user endpoints
routes.use("*", authenticateAdmin(db)); // For admin endpoints
Authorization
Role-based access control using user context:
// Check user roles
if (!req.currentUser.is_super) {
throw new CustomError("FORBIDDEN", "Super admin required");
}
// Check specific roles
if (!req.currentUser.roles.includes("merchant_manager")) {
throw new CustomError("FORBIDDEN", "Insufficient permissions");
}