A clean, service-oriented order management system built with NestJS and TypeScript. Designed for maintainability, strict typing, and clear separation of concerns.
SyncFlow is built on the principle of Modular Service-Oriented Architecture.
Complex business logic does not require complex infrastructure. By leveraging Dependency Injection and clearly defined module boundaries, SyncFlow proves that a well-structured SOA system is readable, testable, and highly maintainable β without the overhead of asynchronous messaging infrastructure.
Each service owns its domain. Each module has one responsibility. The result is a codebase that any engineer can open, understand, and extend on day one.
We follow the standard NestJS layered approach, ensuring each module is fully responsible for its own domain logic and nothing else.
src/
βββ orders/
β βββ orders.module.ts
β βββ orders.controller.ts
β βββ orders.service.ts
β βββ dto/
β β βββ create-order.dto.ts
β β βββ update-order.dto.ts
β βββ entities/
β βββ order.entity.ts
βββ inventory/
β βββ inventory.module.ts
β βββ inventory.service.ts
β βββ entities/
β βββ product.entity.ts
βββ notifications/
β βββ notifications.module.ts
β βββ notifications.service.ts
β βββ notification.interface.ts # provider-agnostic contract + DI token
βββ health/
β βββ health.module.ts
β βββ health.controller.ts # liveness + DB readiness probe
βββ common/
β βββ filters/
β βββ http-exception.filter.ts # consistent error responses
βββ database/
β βββ database.module.ts # TypeORM + SQLite configuration
βββ app.module.ts
βββ main.ts
Order Module
Acts as the orchestrator. It handles the full transaction flow β from receiving the request, through stock validation, to sending the final confirmation. It coordinates InventoryService and NotificationService via constructor injection, keeping the flow readable and synchronous.
Inventory Module Encapsulates all stock logic. It validates availability and manages reservation locks, ensuring data integrity without leaking business rules into the Order layer.
Notification Module
A dedicated service for user communication. The Orders layer depends only on the INotificationService interface (bound through the NOTIFICATION_SERVICE injection token), so the underlying provider (Email β SMS β Push) can be swapped by binding a different implementation β without touching a single line of core business logic. Notification delivery is treated as a side effect: a failure is logged and isolated so it can never roll back a successfully placed order.
This system is built to show that you understand how to write backend software that scales with a team, not just with traffic.
| Concept | Implementation |
|---|---|
| SOLID Principles | Single Responsibility enforced at every layer β one class, one concern |
| Dependency Injection | Full use of the NestJS IoC container for loose coupling |
| Clean API Design | DTOs with class-validator enforce strict, self-documenting request schemas |
| Modular Boundaries | No module reaches into another module's internals |
| Testability | Services are injected, not instantiated β easy to mock and test in isolation |
| TypeScript Strictness | Full strict mode β no any, no implicit types |
- Node.js
v20+ - npm
v9+
1. Clone the repository
git clone https://github.com/0marii/syncflow-order-api.git
cd syncflow-order-api2. Install dependencies
npm install3. Configure environment variables
cp .env.example .envAll variables have sensible defaults, so this step is optional for local development.
4. Start the development server
npm run start:devThe API will be available at http://localhost:3000, and interactive Swagger
documentation at http://localhost:3000/docs.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port the API listens on |
DATABASE_PATH |
syncflow.sqlite |
SQLite file path (use :memory: for ephemeral storage) |
NODE_ENV |
development |
When production, disables TypeORM schema auto-sync |
| Method | Endpoint | Description |
|---|---|---|
POST |
/orders |
Create a new order |
GET |
/orders |
Retrieve all orders |
GET |
/orders/:id |
Retrieve a single order by ID |
PATCH |
/orders/:id |
Update an order |
DELETE |
/orders/:id |
Delete an order |
GET |
/health |
Liveness and database readiness probe |
GET |
/docs |
Interactive Swagger / OpenAPI documentation |
POST /orders
Content-Type: application/json
{
"productId": "prod_abc123",
"quantity": 2,
"userId": "user_xyz789"
}{
"id": "ord_001",
"productId": "prod_abc123",
"quantity": 2,
"userId": "user_xyz789",
"status": "CONFIRMED",
"createdAt": "2026-01-15T10:30:00.000Z"
}Client
β
βΌ
OrdersController β validates request shape via DTO
β
βΌ
OrdersService β orchestrates the business logic
ββββΆ InventoryService.checkAndReserve() β validates & locks stock
ββββΆ NotificationService.sendConfirmation() β notifies the user
β
βΌ
Response returned to Client
Everything is synchronous, predictable, and easy to trace in a debugger or a code review.
Stock is a shared resource, so the system is designed to stay correct under concurrent load:
- Atomic reservations β
InventoryService.checkAndReserve()reserves stock with a single conditionalUPDATE ... WHERE stock >= :quantity. The check and the decrement happen as one indivisible operation, so two simultaneous orders can never both pass and oversell the same units. - Transactional orchestration β creating, updating, and deleting an order runs inside a database transaction. If updating an order requires re-reserving stock and that fails, the transaction rolls back, leaving inventory and orders perfectly consistent.
- Isolated side effects β notifications run outside the order transaction. A delivery failure is logged but never rolls back a successfully placed order.
All incoming requests are validated using class-validator and class-transformer before they reach the service layer.
// create-order.dto.ts
import { IsString, IsInt, Min, IsNotEmpty } from 'class-validator';
export class CreateOrderDto {
@IsString()
@IsNotEmpty()
productId: string;
@IsInt()
@Min(1)
quantity: number;
@IsString()
@IsNotEmpty()
userId: string;
}Invalid requests are rejected automatically with a descriptive 400 Bad Request response β no manual validation logic required.
Services are designed with injection in mind, making them straightforward to mock and test in complete isolation.
# Run all unit and integration tests
npm run test
# Generate a coverage report
npm run test:covCoverage target: β₯ 80%
Because every dependency is injected, testing OrdersService does not require a real InventoryService. You provide a mock, and you test one concern at a time β exactly as SOLID principles intend.
const mockInventoryService = {
checkAndReserve: jest.fn().mockResolvedValue(true),
};
const module = await Test.createTestingModule({
providers: [
OrdersService,
{ provide: InventoryService, useValue: mockInventoryService },
],
}).compile();- Phase 1: Foundation β NestJS project setup, module scaffolding,
Orderentity definition - Phase 2: Domain Logic β
InventoryServicewith atomic stock validation and reservation logic - Phase 3: Integration β Wire
OrdersServicetoInventoryServiceandNotificationServicevia DI - Phase 4: Validation β Full request validation using
class-validatoron all DTOs - Phase 5: Reliability β Comprehensive unit tests, integration tests, and coverage report
- Phase 6: Persistence & Polish β TypeORM + SQLite, Swagger docs, health checks, global error handling
| Technology | Purpose |
|---|---|
| NestJS | Framework β modules, controllers, services, DI container |
| TypeScript | Strict typing across the entire codebase |
| TypeORM | Persistence layer and transactional data access |
| SQLite (better-sqlite3) | Zero-config embedded database |
| class-validator | Declarative DTO validation |
| class-transformer | Request payload transformation |
| @nestjs/config | Environment-based configuration |
| @nestjs/swagger | OpenAPI documentation at /docs |
| @nestjs/terminus | Health checks at /health |
| Jest | Unit and integration testing |
| ESLint + Prettier | Code quality and consistent formatting |
Distributed under the MIT License. See LICENSE for details.
Β© 2026 Mohammad Al Omari