Backend engineer assessment solution built with NestJS, gRPC, Prisma, PostgreSQL, and an npm workspace monorepo.
- Overview
- What This Project Does
- Architecture
- Project Structure
- Technology Stack
- Services
- Database Design
- API Contracts
- gRPC vs REST
- Swagger and API Docs
- Environment Variables
- Getting Started
- Running the Services
- Testing
- Example Requests
- Additional Documentation
- Notes
This project contains two gRPC microservices:
user-servicewallet-service
The services communicate over gRPC inside a monorepo. When a user is created, the system automatically provisions a wallet for that user.
This repository is designed to demonstrate:
- clean NestJS service boundaries
- gRPC contract-first communication
- Prisma-backed persistence with PostgreSQL
- validation and error handling
- transaction-safe wallet debits
- CI-backed testing, including an integration flow
The system models a simple wallet platform:
user-servicecreates and fetches userswallet-servicemanages wallet balanceswallet-serviceverifies users by callinguser-serviceover gRPCuser-serviceauto-provisions a wallet after successful user creation
User creation flow:
- client calls
user.UserService/CreateUser user-servicewrites the user to PostgreSQLuser-servicecallswallet.WalletService/CreateWalletwallet-serviceverifies the user throughuser.UserService/GetUserById- wallet is created and linked to the user
High-level request flow:
Client
|
+--> user-service (gRPC :50051)
| |
| +--> PostgreSQL
| |
| +--> wallet-service (gRPC :50052)
|
+--> wallet-service (gRPC :50052)
|
+--> user-service (gRPC :50051)
|
+--> PostgreSQL
Design principles used here:
- controllers handle gRPC transport only
- services contain business logic only
- Prisma access is isolated in
PrismaService - inter-service calls are wrapped in dedicated gRPC client classes
- request validation is enforced at the gRPC boundary
checkit-wallet-microservices/
|-- apps/
| |-- user-service/
| `-- wallet-service/
|-- packages/
| |-- prisma/
| `-- proto/
|-- docker/
|-- docs/
`-- integration/
- NestJS 11
- gRPC
- Prisma 7
- PostgreSQL 15
- Docker
- Jest
- GitHub Actions
nestjs-pinofor structured logging
gRPC package: user
Default port: 50051
Methods:
CreateUserGetUserById
Responsibilities:
- create users
- fetch users by id
- auto-create wallets after user creation
- roll back user creation if wallet provisioning fails
gRPC package: wallet
Default port: 50052
Methods:
CreateWalletGetWalletCreditWalletDebitWallet
Responsibilities:
- create wallets
- fetch wallets by user id
- credit balances atomically
- debit balances using a transaction-safe update strategy
- verify user existence through
user-service
Entity sketch:
+----------------------+
| User |
+----------------------+
| id UUID/STR | PK
| email STRING | UNIQUE
| name STRING |
| createdAt DATETIME |
+----------------------+
|
| 1 : 1
|
+----------------------+
| Wallet |
+----------------------+
| id UUID/STR | PK
| userId STRING | UNIQUE, FK -> User.id
| balance INTEGER |
| createdAt DATETIME |
+----------------------+
Relationship summary:
- one user has one wallet
- one wallet belongs to one user
- deleting a user cascades to the wallet
Detailed design notes are in docs/database-design.md.
Proto files:
The contracts use snake_case payload fields, including:
created_atuser_id
This project uses gRPC as its real runtime API, not REST.
Short comparison:
- REST usually sends JSON over HTTP and is centered around resource URLs such as
/usersor/wallets - gRPC uses
.protocontracts and method calls such asCreateUserorGetWallet - REST is easier to inspect in a browser and is common for public-facing APIs
- gRPC is better suited to internal service-to-service communication because it is strongly typed, contract-first, and more compact on the wire
Why gRPC was used here:
- the assessment is microservice-focused
wallet-serviceneeds to calluser-servicedirectly- proto contracts make the interaction explicit and strongly typed
- the services benefit from a compact, structured transport for internal calls
Important note:
- the real API in this repository is gRPC
- the OpenAPI file in
docs/openapi.yamlis documentation-only and exists to help reviewers understand the request and response payloads
This project is gRPC-first, so there is no hosted Swagger UI endpoint inside the services.
Instead, the repository includes an OpenAPI-style documentation file at:
How to access it:
- Open Swagger Editor
- Copy/paste the contents of
docs/openapi.yaml, or import the file - Use it as a documentation layer for the gRPC request/response payloads
Important note:
- this OpenAPI file is documentation-only
- the actual runtime API is gRPC, not REST
If you want executable request examples, use:
Create a root .env file if it does not already exist:
DATABASE_URL="postgresql://postgres:229494@localhost:5432/wallet_db"Optional runtime overrides:
USER_SERVICE_URL="0.0.0.0:50051"
WALLET_SERVICE_URL="0.0.0.0:50052"These are useful for integration tests or running services on alternate ports.
Install dependencies:
npm installStart PostgreSQL:
npm run docker:upApply Prisma migrations:
npm run prisma:migrateGenerate Prisma client:
npm run prisma:generateRun wallet-service:
npm run dev:walletRun user-service:
npm run dev:userBuild everything:
npm run buildRun user-service tests:
npm run test:userRun wallet-service tests:
npm run test:walletRun integration flow:
npm run test:integrationWhat the integration test covers:
- create user
- verify wallet auto-provisioning
- credit wallet
- debit wallet
- fetch final wallet state
The examples below use grpcurl.
grpcurl -plaintext \
-import-path packages/proto \
-proto user.proto \
-d "{\"email\":\"jane@example.com\",\"name\":\"Jane Doe\"}" \
localhost:50051 user.UserService/CreateUsergrpcurl -plaintext \
-import-path packages/proto \
-proto user.proto \
-d "{\"id\":\"USER_ID_HERE\"}" \
localhost:50051 user.UserService/GetUserByIdgrpcurl -plaintext \
-import-path packages/proto \
-proto wallet.proto \
-d "{\"user_id\":\"USER_ID_HERE\"}" \
localhost:50052 wallet.WalletService/GetWalletgrpcurl -plaintext \
-import-path packages/proto \
-proto wallet.proto \
-d "{\"user_id\":\"USER_ID_HERE\",\"amount\":500}" \
localhost:50052 wallet.WalletService/CreditWalletgrpcurl -plaintext \
-import-path packages/proto \
-proto wallet.proto \
-d "{\"user_id\":\"USER_ID_HERE\",\"amount\":200}" \
localhost:50052 wallet.WalletService/DebitWalletMore examples are available in docs/grpcurl-examples.md.
Postman collection:
- docs/grpcurl-examples.md
- docs/Checkit-Wallet-Microservices.postman_collection.json
- docs/openapi.yaml
- docs/database-design.md
wallet-servicedepends onuser-servicefor user verification- validation is enforced with
class-validator - known application failures are mapped to explicit gRPC status codes
- integration tests run services on isolated ports to avoid clashes with local dev processes
- CI runs type checks, unit tests, and the integration flow