← Back to list

Building a Scalable Smart City API with Node.js, Microservices, and Grafana

A deep dive into the backend architecture required to power the services of a modern smart city, built with TypeScript, Docker, and a focus…

Jabbar A. Panggabean · 2025-06-11 03:31 · 0 claps · 3.9 min read
#microservices #smart-city-software #api
Open on Medium ↗
Wiki topics: 🌐 · Web Development ☁️ · DevOps & Cloud ⏱️ · Productivity 🏛️ · Architecture 🚆 · Urban & Transport

Building a Scalable Smart City API with Node.js, Microservices, and Grafana

Smartcity Resident API — Grafana Dashboard

Smartcity Resident API — Grafana Dashboard

A deep dive into the backend architecture required to power the services of a modern smart city, built with TypeScript, Docker, and a focus on observability.

As our world becomes more connected, the concept of a “Smart City” is rapidly moving from science fiction to reality. These intelligent urban environments promise to optimize everything from traffic flow to public services through a network of connected devices and data platforms. But what does the backend infrastructure for such a vision look like?

Driven by this question, I set out to build a passion project: the Smart City Resident API. It’s a backend simulation of core resident services designed to explore the challenges of creating a scalable, observable, and robust distributed system.

In this article, I’ll walk you through the architecture, the technology choices, and the key learnings from the project.

You can find the complete source code on GitHub: **github.com/jabbar-app/smartcity-resident-api**

The Core Idea: Simulating Resident Services

The goal was to create a set of APIs that could serve as the foundation for a resident-facing application. I decided to focus on three core domains:

  1. Resident Service: Manages resident profiles, registration, and identity.
  2. Report Service: Allows residents to submit daily reports, which could be anything from health check-ins to community feedback.
  3. Alert Service: Manages the creation and broadcasting of system-wide alerts (e.g., emergency notifications, community event reminders).

With these domains in mind, the next crucial decision was the architecture.

1. Architecture: Why Microservices?

Instead of building a single monolithic application, I chose a microservice architecture. Each of the core domains (Resident, Report, Alert) was developed as a separate, independently deployable service.

This approach offers several key advantages for a system like this:

  • Scalability: Each service can be scaled independently. If the Report Service experiences high traffic, we can allocate more resources to it without affecting the Resident or Alert services.
  • Separation of Concerns: Each microservice has a clearly defined boundary and responsibility. This makes the codebase easier to understand, maintain, and test.
  • Technology Flexibility: While all services in this project use the same stack, a microservice architecture allows different teams to use different technologies for different services if needed.

Here’s a high-level look at how the services interact:

2. The Tech Stack

Choosing the right tools was critical. I prioritized performance, developer experience, and modern best practices.

  • Runtime & Language: Node.js and TypeScript. Node.js is perfect for building fast, I/O-intensive services, and TypeScript adds a crucial layer of type safety, making the code more robust and easier to refactor.
  • Web Framework: Fastify. I chose Fastify over more traditional frameworks like Express because of its primary focus on performance and low overhead. Its plugin-based architecture is also excellent for keeping code organized.
  • Database & ORM: PostgreSQL and Prisma. PostgreSQL is a powerful and reliable open-source relational database. Prisma serves as a next-generation ORM that makes database interactions intuitive and type-safe, which complements TypeScript perfectly.
  • Containerization: Docker and Docker Compose. Containerizing the application ensures that the development environment is identical to the production environment. Docker Compose makes it incredibly simple to define and run the entire multi-container application stack with a single command.

3. Built-in Observability: You Can’t Fix What You Can’t See

In a distributed system with multiple moving parts, observability is not a luxury — it’s a necessity. When an issue occurs, you need to be able to quickly pinpoint which service is failing and why.

To address this, I integrated a simple but powerful monitoring stack:

  • Loki: A horizontally-scalable, multi-tenant log aggregation system by Grafana. It is designed to be cost-effective and easy to operate.
  • Grafana: The popular open-source platform for monitoring and observability. I used Grafana to connect to Loki as a data source, allowing me to visualize, query, and analyze logs from all microservices in a single, unified dashboard.

This setup provides a powerful foundation for real-time monitoring and debugging.

4. Getting It Running Yourself

One of the main goals was to make the project easy for anyone to run locally. Thanks to Docker, you can get the entire system running with just two commands.

Prerequisites: You must have Docker and Docker Compose installed.

Step 1: Clone the repository

git clone https://github.com/jabbar-app/smartcity-resident-api.git
cd smartcity-resident-api

Step 2: Build and run the services This single command will build the Docker images, start all the containers (three API services, PostgreSQL, Grafana, Loki), and set up the database.

docker-compose up --build

The system is now live on your local machine!

  • API Services: Accessible at http://localhost:3001, 3002, and 3003.
  • Grafana Dashboard: View the logs at [http://localhost:3000.](http://localhost:3000.)

You can test the API by registering a new resident:

curl -X POST -H "Content-Type: application/json" \
  -d '{"name": "Budi Santoso", "email": "budi.s@example.com", "password": "password123"}' \
  http://localhost:3001/resident

Final Thoughts and Key Takeaways

Building this Smart City API was a fantastic journey in applying architectural principles to a real-world problem. It reinforced the importance of:

  • Designing for Scale: Thinking in terms of independent services from the start pays dividends later.
  • Prioritizing Observability: A good monitoring setup is essential for maintaining and debugging distributed systems.
  • Leveraging Modern Tooling: Tools like TypeScript, Prisma, and Docker significantly improve developer productivity and code quality.

This project only scratches the surface of what a full-fledged smart city platform would require, but it serves as a solid foundation. What other services do you think are critical for a smart city? I’d love to hear your thoughts and feedback!

If you enjoyed this article, feel free to connect with me on Linkedin!


메타데이터
post_id
fe6ca92b97bf
slug
building-a-scalable-smart-city-api-with-node-js-microservices-and-grafana-fe6ca92b97bf
url
https://medium.com/@jabbarpanggabean/building-a-scalable-smart-city-api-with-node-js-microservices-and-grafana-fe6ca92b97bf
canonical_url
https://medium.com/@jabbarpanggabean/building-a-scalable-smart-city-api-with-node-js-microservices-and-grafana-fe6ca92b97bf
author_url
https://medium.com/@jabbarpanggabean
status
ok
fetched_at
2026-08-01 16:01:30