18 KiB
Elysia API Documentation
Overview
This project uses ElysiaJS integrated with Next.js App Router to create high-performance, type-safe APIs. The codebase follows a Feature-based MVC pattern for maintainability and scalability.
Base URL
http://localhost:3000
Endpoints
Customers API
Get All Customers by Branch
GET /api/customers/:branch
Parameters:
branch(path parameter, required): Branch identifier- Examples:
branch-01,branch-02,head-office
- Examples:
status(query parameter, optional): Filter by customer status- Values:
active,inactive,pending
- Values:
Examples:
- Get all customers from branch-01:
curl http://localhost:3000/api/customers/branch-01
- Get active customers from branch-02:
curl "http://localhost:3000/api/customers/branch-02?status=active"
- Get pending customers from head-office:
curl "http://localhost:3000/api/customers/head-office?status=pending"
Response Format:
{
"success": true,
"data": [
{
"id": "cust-001",
"branch": "branch-01",
"name": "สมชาย ใจดี",
"email": "somchai@example.com",
"phone": "081-234-5678",
"company": "บริษัท ไทยธุรกิจ จำกัด",
"address": "123 ถนนสุขุมวิท แขวงคลองตัน เขตคลองเตย กรุงเทพฯ 10110",
"status": "active",
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:00:00Z"
}
],
"count": 1,
"message": "Found 1 customer(s) for branch: branch-01"
}
Get Single Customer by ID
GET /api/customers/:branch/:id
Create Customer
POST /api/customers
Update Customer
PUT /api/customers/:branch/:id
Delete Customer
DELETE /api/customers/:branch/:id
Quotations API
Get All Quotations by Branch
GET /api/quotations/:branch
Parameters:
branch(path parameter, required): Branch identifier- Examples:
branch-01,branch-02,head-office
- Examples:
status(query parameter, optional): Filter by quotation status- Values:
draft,sent,accepted,rejected,expired
- Values:
Examples:
- Get all quotations from branch-01:
curl http://localhost:3000/api/quotations/branch-01
- Get sent quotations from head-office:
curl "http://localhost:3000/api/quotations/head-office?status=sent"
Response Format:
{
"success": true,
"data": [
{
"id": "quot-001",
"quotationNumber": "QT-2024-001",
"branch": "branch-01",
"customerId": "cust-001",
"customerName": "สมชาย ใจดี",
"date": "2024-01-20T00:00:00Z",
"validUntil": "2024-02-20T00:00:00Z",
"subtotal": 50000,
"taxRate": 0.07,
"taxAmount": 3500,
"totalAmount": 53500,
"status": "sent",
"notes": "Quotation for office supplies",
"createdAt": "2024-01-20T09:00:00Z",
"updatedAt": "2024-01-20T09:00:00Z"
}
],
"count": 2,
"message": "Found 2 quotation(s) for branch: branch-01"
}
Get Single Quotation by ID
GET /api/quotations/:branch/:id
Create Quotation
POST /api/quotations
Update Quotation
PUT /api/quotations/:branch/:id
Delete Quotation
DELETE /api/quotations/:branch/:id
Master Options API
Get All Master Options
GET /api/master-options
Query Parameters:
category(optional): Filter by category (e.g.,customer_type,payment_method,industry)isActive(optional): Filter by active status (trueorfalse)search(optional): Search in code, nameTh, or nameEn
Example:
curl "http://localhost:3000/api/master-options?category=customer_type&isActive=true"
Response Format:
{
"success": true,
"data": [
{
"id": "opt-001",
"branchId": "branch-01",
"category": "customer_type",
"code": "CORPORATE",
"nameTh": "องค์กร/บริษัท",
"nameEn": "Corporate",
"descriptionTh": "ลูกค้าประเภทองค์กร",
"descriptionEn": "Corporate customers",
"isActive": true,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:00:00Z"
}
],
"count": 1,
"message": "Found 1 master option(s)"
}
Get Single Master Option
GET /api/master-options/:id
Create Master Option
POST /api/master-options
Request Body:
{
"category": "customer_type",
"code": "INDIVIDUAL",
"nameTh": "บุคคลธรรมดา",
"nameEn": "Individual",
"descriptionTh": "ลูกค้ารายบุคคล",
"descriptionEn": "Individual customers"
}
Update Master Option
PUT /api/master-options/:id
Delete Master Option
DELETE /api/master-options/:id
Toggle Active Status
PATCH /api/master-options/:id/toggle
Locations API
Get All Locations
GET /api/locations
Query Parameters:
type(optional): Filter by location type (country,province,district,subdistrict)parentId(optional): Filter by parent location IDsearch(optional): Search in code, nameTh, or nameEnisActive(optional): Filter by active status
Example:
curl "http://localhost:3000/api/locations?type=province&search=กรุงเทพ"
Response Format:
{
"success": true,
"data": [
{
"id": "loc-001",
"branchId": "head-office",
"code": "TH-10",
"nameTh": "กรุงเทพมหานคร",
"nameEn": "Bangkok",
"type": "province",
"parentId": "country-th-id",
"isActive": true,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:00:00Z"
}
],
"count": 1,
"message": "Found 1 location(s)"
}
Get Locations by Type
GET /api/locations/type/:type
Parameters:
type(path parameter):country,province,district, orsubdistrict
Example:
curl http://localhost:3000/api/locations/type/province
Get Single Location
GET /api/locations/:id
Create Location
POST /api/locations
Request Body:
{
"code": "TH-10",
"nameTh": "กรุงเทพมหานคร",
"nameEn": "Bangkok",
"type": "province",
"parentId": "country-th-id"
}
Update Location
PUT /api/locations/:id
Delete Location
DELETE /api/locations/:id
Toggle Active Status
PATCH /api/locations/:id/toggle
Industrial Estates API
Get All Industrial Estates
GET /api/industrial-estates
Query Parameters:
locationId(optional): Filter by location IDisActive(optional): Filter by active statussearch(optional): Search in code, nameTh, or nameEn
Example:
curl "http://localhost:3000/api/industrial-estates?locationId=th-10&isActive=true"
Response Format:
{
"success": true,
"data": [
{
"id": "ie-001",
"branchId": "head-office",
"code": "BPL",
"nameTh": "นิคมอุตสาหกรรมบางพลี",
"nameEn": "Bangpoo Industrial Estate",
"locationId": "th-10",
"latitude": 13.5991,
"longitude": 100.7015,
"isActive": true,
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2024-01-15T09:00:00Z"
}
],
"count": 1,
"message": "Found 1 industrial estate(s)"
}
Get Industrial Estates by Location
GET /api/industrial-estates/location/:locationId
Example:
curl http://localhost:3000/api/industrial-estates/location/th-10
Get Single Industrial Estate
GET /api/industrial-estates/:id
Create Industrial Estate
POST /api/industrial-estates
Request Body:
{
"code": "BPL",
"nameTh": "นิคมอุตสาหกรรมบางพลี",
"nameEn": "Bangpoo Industrial Estate",
"locationId": "th-10",
"latitude": 13.5991,
"longitude": 100.7015
}
Update Industrial Estate
PUT /api/industrial-estates/:id
Delete Industrial Estate
DELETE /api/industrial-estates/:id
Toggle Active Status
PATCH /api/industrial-estates/:id/toggle
Audit Logs API
Note: This API requires Admin/Superadmin/Auditor access level.
Get All Audit Logs
GET /api/audit-logs
Query Parameters:
startDate(optional): Filter logs from this date (ISO 8601 format)endDate(optional): Filter logs until this date (ISO 8601 format)action(optional): Filter by action type (CREATE,UPDATE,DELETE,READ)entityType(optional): Filter by entity type (customer,quotation,location, etc.)limit(optional): Number of results to return (default: 50)offset(optional): Number of results to skip (for pagination)
Example:
curl "http://localhost:3000/api/audit-logs?action=CREATE&limit=10"
Response Format:
{
"success": true,
"data": [
{
"id": "audit-001",
"branchId": "branch-01",
"userId": "user-123",
"actorId": "user-123",
"entityType": "customer",
"entityId": "cust-001",
"action": "CREATE",
"actionTh": "สร้าง",
"oldValues": null,
"newValues": {
"name": "สมชาย ใจดี",
"email": "somchai@example.com"
},
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"createdAt": "2024-01-15T09:00:00Z"
}
],
"count": 1,
"message": "Found 1 audit log(s)"
}
Get Audit Log Statistics
GET /api/audit-logs/stats
Response Format:
{
"success": true,
"data": {
"totalLogs": 1250,
"byAction": {
"CREATE": 350,
"UPDATE": 500,
"DELETE": 150,
"READ": 250
},
"byEntityType": {
"customer": 400,
"quotation": 300,
"location": 200,
"industrial_estate": 100,
"master_option": 250
},
"todayCount": 45,
"thisWeekCount": 320
}
}
Get Logs by Entity
GET /api/audit-logs/entity/:entityType/:entityId
Example:
curl http://localhost:3000/api/audit-logs/entity/customer/cust-001
Get Logs by User
GET /api/audit-logs/user/:userId
Example:
curl http://localhost:3000/api/audit-logs/user/user-123
Get Single Audit Log
GET /api/audit-logs/:id
Available Data
Customers
branch-01: 4 customers (3 active, 1 pending)branch-02: 3 customers (1 active, 1 inactive, 1 pending)head-office: 3 customers (all active)
Quotations
branch-01: 2 quotations (1 sent, 1 accepted)branch-02: 1 quotation (draft)head-office: 1 quotation (sent)
Master Options
- Categories:
customer_type,payment_method,industry,lead_source - Each category has multiple options with Thai/English names
Locations
- Countries: Thailand, etc.
- Provinces: All Thai provinces
- Districts/Subdistricts: Hierarchical data structure
Industrial Estates
- Multiple industrial estates across Thailand
- Linked to locations with GPS coordinates
Audit Logs
- Complete audit trail for all operations
- Admin-only access
Testing with Browser
Simply open these URLs in your browser:
Customers
- http://localhost:3000/api/customers/branch-01
- http://localhost:3000/api/customers/branch-02?status=active
- http://localhost:3000/api/customers/head-office
Quotations
- http://localhost:3000/api/quotations/branch-01
- http://localhost:3000/api/quotations/head-office?status=sent
Master Options
- http://localhost:3000/api/master-options
- http://localhost:3000/api/master-options?category=customer_type
Locations
- http://localhost:3000/api/locations
- http://localhost:3000/api/locations/type/province
- http://localhost:3000/api/locations?search=กรุงเทพ
Industrial Estates
- http://localhost:3000/api/industrial-estates
- http://localhost:3000/api/industrial-estates?isActive=true
Audit Logs (Admin only)
Project Structure
This project follows the Feature-based MVC pattern as recommended by ElysiaJS:
src/
├── app/
│ └── api/
│ └── [[...slugs]]/
│ └── route.ts # Main API entry point
├── modules/
│ ├── customers/
│ │ ├── controller.ts # HTTP handlers & routing
│ │ ├── service.ts # Business logic
│ │ └── model.ts # Schemas & validation
│ ├── quotations/
│ │ ├── controller.ts # HTTP handlers & routing
│ │ ├── service.ts # Business logic
│ │ └── model.ts # Schemas & validation
│ ├── master-options/
│ │ ├── controller.ts # HTTP handlers & routing
│ │ ├── service.ts # Business logic
│ │ └── model.ts # Schemas & validation
│ ├── locations/
│ │ ├── controller.ts # HTTP handlers & routing
│ │ ├── service.ts # Business logic
│ │ └── model.ts # Schemas & validation
│ ├── industrial-estates/
│ │ ├── controller.ts # HTTP handlers & routing
│ │ ├── service.ts # Business logic
│ │ └── model.ts # Schemas & validation
│ └── audit-logs/
│ ├── controller.ts # HTTP handlers & routing
│ ├── service.ts # Business logic
│ └── model.ts # Schemas & validation
├── types/
│ └── customer.ts # Shared types
├── lib/
│ └── mock-data.ts # Mock data
└── database/
└── schema.ts # Drizzle ORM schema
File Responsibilities
Model (model.ts)
- Define TypeBox schemas for validation
- Export TypeScript types from schemas
- All data structure definitions
Service (service.ts)
- Business logic and data operations
- Pure functions (no Elysia dependencies)
- CRUD operations
- Data transformation
Controller (controller.ts)
- Elysia instance for the module
- Route definitions and handlers
- Request/response validation
- Calls service functions
- HTTP-specific concerns
Main Route (app/api/[[...slugs]]/route.ts)
- Import all controllers
- Combine with
.use() - Export handlers for Next.js
Important Implementation Notes
This project follows the correct ElysiaJS + Next.js integration pattern:
- ✅ Single route file
[[...slugs]]/route.tswith Elysia internal routing - ✅ Uses
export const GET = app.fetch(not.handle) - ✅ Elysia instance has
prefix: '/api' - ✅ All routes defined within Elysia instances using
.get(),.post(), etc. - ✅ WinterCG compliant - works as normal Next.js API route
- ✅ Feature-based MVC pattern for maintainability
- ✅ Clear separation of concerns between Model, View, and Controller
- ✅ Branch-level data scoping for multi-tenant architecture
- ✅ Audit logging for all operations
- ✅ Soft delete with
deletedAtfield - ✅ Multi-language support (Thai/English)
Technologies Used
- ElysiaJS: Type-safe, high-performance web framework
- Next.js 16: React framework with App Router
- TypeScript: Type safety throughout
- TypeBox: Schema validation (via
@elysiajs/schema) - Drizzle ORM: Type-safe SQL ORM
- PostgreSQL: Primary database
Features
✅ Feature-based MVC architecture ✅ Dynamic branch parameter support ✅ Type-safe request/response validation ✅ Optional query parameter filtering ✅ Mock data for customers and quotations ✅ Full TypeScript support ✅ Auto-generated API documentation (Swagger/OpenAPI ready) ✅ Correct ElysiaJS + Next.js integration pattern ✅ Scalable and maintainable code structure ✅ Clear separation of concerns ✅ Multi-tenant architecture with branch scoping ✅ Complete audit logging system ✅ Soft delete for data integrity ✅ Multi-language support (Thai/English) ✅ Hierarchical data structures (locations) ✅ GPS coordinate support (industrial estates) ✅ Admin-only access control (audit logs)
Adding New Modules
To add a new module (e.g., products):
-
Create folder:
src/modules/products/ -
Create
model.ts- Define schemas -
Create
service.ts- Business logic -
Create
controller.ts- Routes and handlers -
Create
index.ts- Module exports -
Update
src/app/api/[[...slugs]]/route.ts:import { products } from "@/modules/products/controller"; const app = new Elysia({ prefix: "/api" }) .use(customers) .use(quotations) .use(masterOptions) .use(locations) .use(industrialEstates) .use(auditLogs) .use(products); // Add new module
Security & Access Control
Branch Middleware
All routes use branchMiddleware which injects:
currentBranchId- Current user's branchuserId- Current user IDuserGroups- User groups/rolesaccessibleBranches- Branches user can access
Permission Levels
- Standard Users: Access to branch-scoped data
- Admin/Superadmin: Full access + audit logs
- Auditor: Read-only access to audit logs
Data Isolation
- All queries are automatically filtered by
branchId - Cross-branch access is prevented
- Soft delete ensures data integrity