REST service that ingests soil moisture readings from field sensors and persists them to Postgres. TypeScript on Express, Sequelize for the data layer, Docker Compose for the runtime.
The interesting part is the layering: routes hold no logic, the controller only translates
HTTP, and the service depends on a repository interface rather than Sequelize. Swapping
Postgres for anything else means writing one new class and changing one line in
routes/sensorRoutes.ts.
- Node 18, TypeScript 5
- Express 4
- Sequelize 6 + PostgreSQL 15
- Docker + Docker Compose
routes/ HTTP surface, wires the dependency graph
controllers/ request/response translation only
services/ business logic, depends on ISensorRepository
repositories/ ISensorRepository + Sequelize implementation
models/ Sequelize model definition
config/ database connection, Dockerfile, compose file
src/ app assembly and bootstrap
Dependencies are injected through constructors and the graph is built once in
routes/sensorRoutes.ts. Nothing below the controller layer knows Express exists.
POST /api/sensor
{ "moisture": 42.5 }Returns 201 with the stored row. Missing moisture returns 400, a write failure 500.
GET /api/sensor
Returns every stored reading as an array. No pagination or filtering yet.
Table sensor_data, created by sequelize.sync() on boot:
| Column | Type | Note |
|---|---|---|
| id | integer | primary key, auto increment |
| moisture | float | not null |
| createdAt | timestamp | |
| updatedAt | timestamp |
Read from the environment via dotenv:
| Variable | Purpose |
|---|---|
POSTGRES_DB |
database name |
POSTGRES_USER |
database user |
POSTGRES_PASSWORD |
database password |
PORT |
HTTP port, defaults to 3000 |
Create a .env next to the compose file. It is gitignored.
Note that config/database.ts pins the database host to db, the compose service name, so
the app expects to run inside the compose network. Running it bare with npm run dev against
a local Postgres means changing that host.
docker compose -f config/docker-compose.yml up --buildBrings up Postgres 15 with a named volume and the API on port 3000. The container runs
npm install && npm run build on start, so source changes need a restart, not a rebuild.
Without Docker:
npm install
npm run dev # ts-node + nodemon
npm run build # tsc to dist/
npm start # node dist/server.jsISC