Event-driven banking microservices built with NestJS and Apache Kafka, deployed to IBM Cloud Code Engine.
Status: prototype / learning project. The services run and exchange real Kafka events, but several parts are intentionally simplified for demonstration. Account balances live in memory, and the notification service logs messages instead of sending real emails. See the Roadmap for what is and is not implemented.
- About
- Features
- Tech Stack
- Architecture
- Getting Started
- Project Structure
- Configuration
- Roadmap
- Contributing
- License
IntelliBank-Kafka is a small banking platform split into three independent NestJS services that talk to each other through Apache Kafka topics instead of direct HTTP calls. It shows how an event-driven design decouples services so that creating a customer, processing a transaction, and sending a notification can each scale and fail on their own.
The project is a NestJS monorepo. Each service is its own application with its own entry point, Dockerfile, and Kafka client. The services connect to a managed Kafka cluster (IBM Event Streams) over SASL/SSL, and a GitHub Actions workflow builds the Docker images and deploys them to IBM Cloud Code Engine.
The repository name uses "IntelliBank" as a brand. The domain is banking-style customer and transaction handling; the focus of the code is the Kafka event flow rather than real banking logic.
- Three decoupled microservices:
customer-service,transaction-service, andnotification-service. - Asynchronous communication over Apache Kafka using
kafkajsthrough@nestjs/microservices. - A shared
KafkaConnectionUtilshelper that standardizes the Kafka client config (SASL/SSL, timeouts, retries, idempotent producer) across all services. - Broker list parsing that accepts either a JSON array or a comma-separated string from one environment variable.
- Transaction processing with sender/recipient checks and insufficient-funds handling, emitting success, failure, and error events.
- Notification handlers that react to customer and transaction events.
- One Dockerfile per service and a GitHub Actions pipeline that deploys to IBM Cloud Code Engine.
| Layer | Technology |
|---|---|
| Language | TypeScript |
| Framework | NestJS 10 (monorepo) |
| Messaging | Apache Kafka via kafkajs and @nestjs/microservices |
| HTTP | Express (@nestjs/platform-express) |
| Package manager | pnpm |
| Containers | Docker (node:18-alpine) |
| Cloud | IBM Cloud Code Engine and IBM Event Streams |
| Container registry | IBM Container Registry (au.icr.io) |
| CI/CD | GitHub Actions |
| Testing | Jest |
Each service exposes an HTTP endpoint and also runs as a Kafka microservice. Producers emit events to topics; consumers subscribe with @MessagePattern. The notification service is a pure consumer.
flowchart LR
Client[HTTP Client]
subgraph Services
CS[customer-service<br/>port 3000]
TS[transaction-service<br/>port 3002]
NS[notification-service<br/>port 3001]
end
Kafka[(Apache Kafka<br/>IBM Event Streams)]
Client -->|POST /customer| CS
Client -->|POST /transactions| TS
CS -->|emit customer.created| Kafka
TS -->|emit transaction.requested| Kafka
Kafka -->|transaction.requested| TS
TS -->|emit transaction.processed / failed / error| Kafka
Kafka -->|customer.created| NS
Kafka -->|transaction.processed| NS
Topics used in the code:
| Topic | Produced by | Consumed by |
|---|---|---|
customer.created |
customer-service | notification-service |
transaction.requested |
transaction-service (HTTP) | transaction-service (processor) |
transaction.processed |
transaction-service | notification-service |
transaction.failed |
transaction-service | (emitted, not yet consumed) |
transaction.error |
transaction-service | (emitted, not yet consumed) |
- Node.js 18 or newer
- pnpm
- Access to a Kafka cluster. The code is set up for IBM Event Streams with SASL/SSL, so you need broker addresses plus a user and password.
node --version
pnpm --versiongit clone https://github.com/atiqbitstream/IntelliBank-Kafka.git
cd IntelliBank-Kafka
pnpm installThe services read Kafka settings from environment variables (see Configuration). Set them in your shell or a .env file before starting a service.
export EVENT_STREAMS_KAFKA_BROKERS_SASL='["broker-1:9093","broker-2:9093"]'
export EVENT_STREAMS_USER='your-kafka-user'
export EVENT_STREAMS_PASSWORD='your-kafka-password'This is a NestJS monorepo, so you start one service at a time by name.
# Customer service on port 3000
pnpm exec nest start customer-service
# Transaction service on port 3002
pnpm exec nest start transaction-service
# Notification service on port 3001
pnpm exec nest start notification-serviceAdd --watch for hot reload during development, for example pnpm exec nest start customer-service --watch.
Create a customer (emits customer.created):
curl -X POST http://localhost:3000/customer \
-H "Content-Type: application/json" \
-d '{"id":"cust-001","name":"John Doe","email":"john@example.com"}'Initiate a transaction (emits transaction.requested, then processed/failed/error):
curl -X POST http://localhost:3002/transactions \
-H "Content-Type: application/json" \
-d '{"from":"cust-001","to":"cust-002","amount":100,"transactionId":"txn-123"}'The notification service has no HTTP endpoint to call. Watch its logs to see it react to the events above. The transaction service seeds two demo accounts in memory, cust-001 with 1000 and cust-002 with 500.
Each service has its own Dockerfile.
docker build -t customer-service -f Dockerfile.customer-service .
docker build -t transaction-service -f Dockerfile.transaction-service .
docker build -t notification-service -f Dockerfile.notification-service .Pushing to the master branch triggers .github/workflows/deploy.yml, which logs in to IBM Cloud, builds and pushes each image to au.icr.io, and updates the matching Code Engine applications. The workflow expects an IBM_CLOUD_API_KEY repository secret and existing Code Engine applications named customer-service, transaction-service, and notification-service.
.
├── apps/
│ ├── customer-service/
│ │ ├── src/ # controller, service, module, main
│ │ └── kafka-connection.utils.ts # shared Kafka client config
│ ├── transaction-service/
│ │ ├── src/ # transaction processing + events
│ │ └── kafka-connection.utils.ts
│ └── notification-service/
│ ├── src/ # event consumers
│ └── kafka-connection.utils.ts
├── Dockerfile.customer-service
├── Dockerfile.transaction-service
├── Dockerfile.notification-service
├── .github/workflows/deploy.yml # CI/CD to IBM Cloud Code Engine
├── nest-cli.json # monorepo project definitions
└── package.json
All services read the same Kafka variables through KafkaConnectionUtils.
| Variable | Description | Default |
|---|---|---|
EVENT_STREAMS_KAFKA_BROKERS_SASL |
Kafka broker list, as a JSON array or a comma-separated string | none (required; empty list logs an error) |
EVENT_STREAMS_USER |
SASL username for the Kafka cluster | empty string |
EVENT_STREAMS_PASSWORD |
SASL password for the Kafka cluster | empty string |
PORT |
HTTP port for the service | 3000 (customer), 3001 (notification), 3002 (transaction) |
The Kafka client always uses SSL with the SASL plain mechanism and an idempotent producer with three retries.
- Persist customers and account balances in a database instead of in-memory maps.
- Send real notifications (email or SMS) from the notification service instead of logging.
- Add consumers for
transaction.failedandtransaction.errorevents. - Add input validation and DTOs on the HTTP endpoints.
- Provide a local
docker-composewith a Kafka broker for offline development. - Expand unit and end-to-end test coverage.
Contributions are welcome. Open an issue to discuss a change, then send a pull request. Please run pnpm lint and pnpm test before submitting.
Distributed under the MIT License. See LICENSE.