A microservices-based user activity tracking system built with Node.js, TypeScript, Drizzle ORM and PostgreSQL. The system demonstrates a clean separation of concerns with dedicated services for reading and writing operations.
This project consists of three main components:
- Purpose: handles all write operations (CREATE, UPDATE, DELETE)
- Database user:
writer_user(full CRUD permissions)
- Purpose: handles all read operations (SELECT queries only)
- Database User:
reader_user(read-only permissions)
- Purpose: contains database schema, migrations and shared utilities
- Database: PostgreSQL with Drizzle ORM
- Features: database migrations, schema definitions, validation schemas
- Docker
- Node.js (v18 or higher)
- npm
docker-compose up -dThis will start a PostgreSQL container with:
- Database:
activity_tracker - User:
postgres - Password:
postgres - Port:
5432
The database users are automatically created when the container starts up (via the init script).
# Install shared package dependencies
cd shared
npm install
# Install writer service dependencies
cd writer
npm install
# Install reader service dependencies
cd reader
npm installcd shared
npm run db:migrate-and-grant-accessThis script will:
- run the database migrations
- apply the appropriate permissions for
reader_userandwriter_user
Terminal 1 - start writer service:
cd writer
npm run devThe writer service will be available at http://localhost:3000
Terminal 2 - start reader service:
cd reader
npm run devThe reader service will be available at http://localhost:3001
The system implements a robust security model with role-based access:
- Permissions: SELECT, INSERT, UPDATE, DELETE
- Purpose: full CRUD operations on all tables
- Used by: writer service for creating/updating data
- Permissions: SELECT only
- Purpose: read-only access to all tables
- Used by: reader service for querying data
GET /users- List all usersPOST /users- Create a new userPUT /users/:id- Update a userPATCH /users/:id- Update a user and an activityDELETE /users/:id- Delete a userGET /activities- List all activitiesPOST /activities- Create a new activityPUT /activities/:id- Update an activityDELETE /activities/:id- Delete an activity
GET /users- List all usersGET /users/:id- Get a specific userGET /activities- List all activitiesGET /activities/:id- Get a specific activity
This project demonstrates the use of Drizzle ORM transactions to ensure data consistency across multiple operations. Two key examples showcase transaction usage:
When creating a user via POST /users, the system uses a transaction to:
- insert the new user record
- automatically create an activity record if the user is 40+ years old (longevity badge)
This ensures both operations succeed or fail together, maintaining data integrity.
The PATCH /users/:id endpoint demonstrates nested transactions:
- updates user information (name, age)
- creates an activity record to track the update
- uses nested transactions for complex business logic
Transactions are implemented using Drizzle's db.transaction() method, providing ACID guarantees for multi-step operations.
user-activity-tracker/
├── docker-compose.yml # Database container configuration
├── shared/ # Shared database and utilities
│ ├── db/
│ │ ├── schema/ # Drizzle ORM schema definitions
│ │ └── migrations/ # Database migration files
│ ├── sql/ # SQL scripts for user setup
│ └── services/ # Shared business logic
├── writer/ # Write operations service
│ └── src/
│ ├── routes/ # API route handlers
│ └── services/ # Business logic
└── reader/ # Read operations service
└── src/
├── routes/ # API route handlers
└── services/ # Business logic
To create a new migration:
cd shared
npm run db:generateTo apply migrations:
cd shared
npm run db:migrateCreate .env files in each service directory if needed:
shared/.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/activity_tracker
writer/.env:
DATABASE_URL=postgres://writer_user:password@localhost:5432/activity_tracker
reader/.env:
DATABASE_URL=postgres://reader_user:password@localhost:5432/activity_tracker