Skip to content

anajembaedwin/checkit-wallet-microservices

Repository files navigation

Checkit Wallet Microservices

Node.js NestJS gRPC Prisma PostgreSQL Docker Jest CI

Backend engineer assessment solution built with NestJS, gRPC, Prisma, PostgreSQL, and an npm workspace monorepo.

Table of Contents

Overview

This project contains two gRPC microservices:

  • user-service
  • wallet-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

What This Project Does

The system models a simple wallet platform:

  • user-service creates and fetches users
  • wallet-service manages wallet balances
  • wallet-service verifies users by calling user-service over gRPC
  • user-service auto-provisions a wallet after successful user creation

User creation flow:

  1. client calls user.UserService/CreateUser
  2. user-service writes the user to PostgreSQL
  3. user-service calls wallet.WalletService/CreateWallet
  4. wallet-service verifies the user through user.UserService/GetUserById
  5. wallet is created and linked to the user

Architecture

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

Project Structure

checkit-wallet-microservices/
|-- apps/
|   |-- user-service/
|   `-- wallet-service/
|-- packages/
|   |-- prisma/
|   `-- proto/
|-- docker/
|-- docs/
`-- integration/

Technology Stack

  • NestJS 11
  • gRPC
  • Prisma 7
  • PostgreSQL 15
  • Docker
  • Jest
  • GitHub Actions
  • nestjs-pino for structured logging

Services

User Service

gRPC package: user
Default port: 50051

Methods:

  • CreateUser
  • GetUserById

Responsibilities:

  • create users
  • fetch users by id
  • auto-create wallets after user creation
  • roll back user creation if wallet provisioning fails

Wallet Service

gRPC package: wallet
Default port: 50052

Methods:

  • CreateWallet
  • GetWallet
  • CreditWallet
  • DebitWallet

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

Database Design

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.

API Contracts

Proto files:

The contracts use snake_case payload fields, including:

  • created_at
  • user_id

gRPC vs REST

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 /users or /wallets
  • gRPC uses .proto contracts and method calls such as CreateUser or GetWallet
  • 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-service needs to call user-service directly
  • 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.yaml is documentation-only and exists to help reviewers understand the request and response payloads

Swagger and API Docs

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:

  1. Open Swagger Editor
  2. Copy/paste the contents of docs/openapi.yaml, or import the file
  3. 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:

Environment Variables

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.

Getting Started

Install dependencies:

npm install

Start PostgreSQL:

npm run docker:up

Apply Prisma migrations:

npm run prisma:migrate

Generate Prisma client:

npm run prisma:generate

Running the Services

Run wallet-service:

npm run dev:wallet

Run user-service:

npm run dev:user

Build everything:

npm run build

Testing

Run user-service tests:

npm run test:user

Run wallet-service tests:

npm run test:wallet

Run integration flow:

npm run test:integration

What the integration test covers:

  • create user
  • verify wallet auto-provisioning
  • credit wallet
  • debit wallet
  • fetch final wallet state

Example Requests

The examples below use grpcurl.

Create User

grpcurl -plaintext \
  -import-path packages/proto \
  -proto user.proto \
  -d "{\"email\":\"jane@example.com\",\"name\":\"Jane Doe\"}" \
  localhost:50051 user.UserService/CreateUser

Get User By Id

grpcurl -plaintext \
  -import-path packages/proto \
  -proto user.proto \
  -d "{\"id\":\"USER_ID_HERE\"}" \
  localhost:50051 user.UserService/GetUserById

Get Wallet

grpcurl -plaintext \
  -import-path packages/proto \
  -proto wallet.proto \
  -d "{\"user_id\":\"USER_ID_HERE\"}" \
  localhost:50052 wallet.WalletService/GetWallet

Credit Wallet

grpcurl -plaintext \
  -import-path packages/proto \
  -proto wallet.proto \
  -d "{\"user_id\":\"USER_ID_HERE\",\"amount\":500}" \
  localhost:50052 wallet.WalletService/CreditWallet

Debit Wallet

grpcurl -plaintext \
  -import-path packages/proto \
  -proto wallet.proto \
  -d "{\"user_id\":\"USER_ID_HERE\",\"amount\":200}" \
  localhost:50052 wallet.WalletService/DebitWallet

More examples are available in docs/grpcurl-examples.md.

Postman collection:

Additional Documentation

Notes

  • wallet-service depends on user-service for 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

About

A gRPC-based NestJS microservices for user and wallet management with Prisma, PostgreSQL, validation, logging, CI, and end-to-end integration tests.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors