Small REST API for a robo-advisor style order splitter: you send a BUY or SELL with a total dollar amount and a weighted portfolio; the service resolves prices, figures advisory scheduling against market hours, splits the order into per-ticker lines, and stores everything in memory (no database).
You’ll need Node.js (this repo is on TypeScript 6 / modern Node — 20+ is a safe bet).
npm install
cp .env.example .env # optional; defaults match .env.example anyway
npm run build
npm startThe server listens on PORT from env (default 3000). You should see something like Server listening on port 3000.
Dev (auto-reload on src changes):
npm run devTests:
npm testRequests hit Express middleware first (json body, clock, response-time logging), then order routes → OrderController. The controller parses query params for list, validates the POST body with OrderRequestValidator, and delegates to OrderService.
OrderService is the orchestration layer: it uses PriceResolver (fixed default vs per-line market price), MarketScheduler (Luxon + market window) for scheduling, OrderSplitter for per-ticker quantities and rounding, and InMemoryOrderRepository for persistence. Domain types live under src/domain/; ValidationError / NotFoundError are mapped to HTTP status in the global errorHandler.
Rough flow for creating an order:
sequenceDiagram
participant C as Client
participant X as Express
participant Ctrl as OrderController
participant V as OrderRequestValidator
participant S as OrderService
participant P as PriceResolver
participant M as MarketScheduler
participant Sp as OrderSplitter
participant R as InMemoryOrderRepository
C->>X: POST /orders JSON
X->>Ctrl: createOrder
Ctrl->>V: validate body
V-->>Ctrl: ok
Ctrl->>S: createOrder
S->>P: resolve prices
P-->>S: per-line prices
S->>M: advisory scheduledAt
M-->>S: instant
S->>Sp: split order
Sp-->>S: splits
S->>R: save
R-->>S: stored order
S-->>Ctrl: result
Ctrl-->>C: 201 + body
Layered view (same idea, flatter):
flowchart LR
HTTP[HTTP / Express] --> OC[OrderController]
OC --> OV[OrderRequestValidator]
OC --> OSvc[OrderService]
OSvc --> PR[PriceResolver]
OSvc --> MS[MarketScheduler]
OSvc --> OSp[OrderSplitter]
OSvc --> Repo[InMemoryOrderRepository]
Replace the URL host/port if you changed PORT.
curl -s http://localhost:3000/healthOne JSON object = one order. There is no bulk endpoint; fire multiple POSTs if you need several orders.
curl -s -X POST http://localhost:3000/orders \
-H "Content-Type: application/json" \
-d '{
"type": "BUY",
"totalAmount": 100,
"portfolio": [
{ "ticker": "AAPL", "weight": 60, "marketPrice": 189.5 },
{ "ticker": "TSLA", "weight": 40 }
]
}'Notes that tend to matter in practice:
marketPriceon a line is optional; if omitted, the server uses a configured default (see.env.example:DEFAULT_STOCK_PRICE).typeisBUYorSELLonly.
# everything stored so far
curl -s "http://localhost:3000/orders"
# BUY only
curl -s "http://localhost:3000/orders?type=BUY"
# date range: from / to are UTC calendar dates (YYYY-MM-DD), inclusive on createdAt
curl -s "http://localhost:3000/orders?from=2026-01-01&to=2026-12-31"
# combined
curl -s "http://localhost:3000/orders?type=SELL&from=2026-04-01&to=2026-04-30"Use the id from the POST response:
curl -s "http://localhost:3000/orders/<paste-uuid-here>"If NODE_ENV is not production, you can send an ISO-8601 instant so scheduling logic uses that time instead of the real clock:
curl -s -X POST http://localhost:3000/orders \
-H "Content-Type: application/json" \
-H "X-Simulated-Time: 2026-04-06T17:00:00.000Z" \
-d '{"type":"BUY","totalAmount":10,"portfolio":[{"ticker":"X","weight":100}]}'There’s an importable collection under postman/Order_Splitter.postman_collection.json if you prefer clicking instead of curl.
Written answers to the take-home prompts (approach, assumptions, challenges, production considerations, LLM use) are in ANSWERS.md.
See .env.example for all variables. The important ones:
| Variable | Role |
|---|---|
PORT |
HTTP port |
DEFAULT_STOCK_PRICE |
Fallback $/share when request omits marketPrice |
SHARE_QUANTITY_PRECISION / SHARE_QUANTITY_ROUNDING_MODE |
How split share quantities are rounded |
MARKET_TIMEZONE, MARKET_OPEN_TIME, MARKET_CLOSE_TIME |
Session used for advisory scheduledAt |
Runtime
| Package | What it’s for |
|---|---|
| express | HTTP server and routing |
| luxon | Dates/times and market session math (timezone-aware) |
| uuid | Generating order ids |
| dotenv | Loading .env into process.env at startup |
Development / testing
| Package | What it’s for |
|---|---|
| typescript | Typed JS, tsc build |
| ts-node | Run TS directly in npm run dev |
| nodemon | Restart dev server on file changes |
| jest + ts-jest | Unit/integration tests |
| supertest | HTTP assertions against the Express app |
@types/* |
TypeScript typings for the libraries above |
That’s the stack the assignment asked to document; versions are pinned in package.json.
src/server.ts— listen + config loadsrc/app.ts— app factory, wires services and routessrc/routes/— route registrationsrc/controllers/,src/services/,src/repositories/,src/validation/— usual splittests/— Jest tests (unit+integration)