This project is a RESTful API service developed with NestJS that allows users to query location-based weather data. The system incorporates modern and scalable backend architecture principles such as Role-Based Access Control (RBAC), external API integration (OpenWeather API), caching (Redis), database management (PostgreSQL & Prisma), and metric monitoring (Prometheus & Grafana).
- RESTful API: Clean, modular, and scalable API built with NestJS.
- Role-Based Access Control (RBAC): Secure endpoints with
adminanduserroles. - Authentication: Secure user authentication with JWT (JSON Web Tokens).
- Database: Efficient database management and optimization with PostgreSQL and Prisma ORM.
- External API Integration: Fetching real-time weather data from the OpenWeather API.
- Caching: Improving performance and efficiently using API limits by caching frequent requests with Redis.
- Configuration Management: Centralized and environment-based configuration with
@nestjs/config. - Validation: Automatic validation of incoming requests via DTOs using
class-validatorandclass-transformer. - API Documentation: Auto-generated and interactive API documentation with Swagger (OpenAPI).
- Security: Protection against brute-force attacks with Throttling (rate limiting).
- Logging: Comprehensive logging for incoming requests, responses, and errors.
- Monitoring and Metrics: Collection of metrics with Prometheus and visualization with Grafana.
- CORS: Configurable Cross-Origin Resource Sharing.
- Graceful Shutdown: Ensures the application terminates cleanly, preventing data loss.
- Health Check: An endpoint to monitor application status.
- Containerization: Easy setup and deployment with Docker and Docker Compose.
- CI/CD Github Actions for testing and deployment.
The application is built on a monolithic NestJS architecture. Inter-service communication and separation of responsibilities are achieved through a modular structure. The following diagram illustrates the main components of the system and their interactions.
graph TD
subgraph "Client"
UserInterface["User / Admin"]
end
subgraph "Weather API Application (NestJS)"
API["REST API <br> (Throttling, Guards)"]
Auth["Auth Module <br> (JWT)"]
User["User Module"]
Weather["Weather Module"]
Metrics["Metrics Module"]
Cache["Cache Service <br> (Redis)"]
DB["Database Service <br> (Prisma)"]
end
subgraph "External Services"
OpenWeather["OpenWeather API"]
Postgres[(PostgreSQL DB)]
Redis[(Redis Cache)]
Prometheus[Prometheus]
Grafana[Grafana]
end
UserInterface -- "HTTPS" --> API
API --> Auth
API --> User
API --> Weather
API -- "Records Metrics" --> Metrics
Auth -- "Validates User" --> DB
User -- "Manages Users" --> DB
Weather -- "Manages Weather Data" --> DB
Weather -- "Caches Data" --> Cache
Weather -- "Fetches Weather" --> OpenWeather
Cache -- "Uses" --> Redis
DB -- "Uses" --o Postgres
Prometheus -- "Scrapes" --> Metrics
Grafana -- "Visualizes" --> Prometheus
The database schema includes several indexes to optimize query performance. Below is a list of these indexes and their purposes:
-
User Model:
emailandusernamehave unique constraints, which also create indexes to prevent duplicates and speed up lookups.
-
WeatherQuery Model:
userId: Indexed to quickly retrieve all queries for a specific user.userId, queryTime(Composite Index): Optimizes queries that filter by both user and the time of the query.city, queryTime(Composite Index): Optimizes queries that filter by both city and the time of the query.
-
WeatherData Model:
weatherQueryId: Indexed with a unique constraint to ensure a fast, direct link to its correspondingWeatherQuery.
These indexes are crucial for maintaining performance, especially as the amount of data grows.
The project follows the standard modular structure of NestJS. The main logic is located under the src/ directory.
/
├── prisma/ # Prisma schema and migration files
│ └── schema.prisma
├── src/
│ ├── common/ # Shared modules (guards, filters, decorators)
│ ├── module/ # Application's main modules
│ │ ├── auth/ # Authentication (login)
│ │ ├── cache/ # Redis cache service
│ │ ├── config/ # Environment variables and configuration
│ │ ├── database/ # Database connection (Prisma)
│ │ ├── metrics/ # Prometheus metrics
│ │ ├── user/ # User management
│ │ └── weather/ # Weather querying
│ ├── main.ts # Application entry point
│ └── app.module.ts # Main application module
├── .env.example # Template for required environment variables
├── docker-compose.yml # Docker Compose configuration
└── package.json # Project dependencies and scripts
- Node.js (v18+)
- Docker and Docker Compose
git clone https://github.com/0xemrekaya/weather-app
cd weather-appCopy the .env.example file to create a new file named .env and edit the values according to your configuration.
cp env.example .envYou can start all services (PostgreSQL, Redis, Grafana, Prometheus, and Weather-App) with a single command using Docker and Docker Compose.
npm run docker:up- The application will be running at
http://localhost:3000. - Prometheus will be accessible at
http://localhost:9090. - Grafana will be accessible at
http://localhost:8080.
To stop the services:
npm run docker:downIf you are running the services (PostgreSQL, Redis) on your local machine, you can start the application locally.
# Install dependencies
npm install
# Run Prisma migrations
npx prisma migrate dev
# Start in development mode
npm run start:devThere is a already created a admin.
username = admin
password = password
The system has two different user roles:
user:- Can only view their own weather query history.
- Can make new weather queries.
admin:- Can list all users.
- Can create new users.
- Can query the weather history of any user by
userId.
Once the application is started, you can access the API documentation at http://localhost:3000/api/docs.
| Method | Endpoint | Description | Role |
|---|---|---|---|
POST |
/api/v1/auth/login |
User login and get JWT token | Public |
| Method | Endpoint | Description | Role |
|---|---|---|---|
GET |
/api/v1/users |
Lists all users | admin |
POST |
/api/v1/users |
Creates a new user | admin |
| Method | Endpoint | Description | Role |
|---|---|---|---|
GET |
/api/v1/weather |
Gets weather data for a specified city | user, admin |
GET |
/api/v1/weather/history |
Gets the query history of the logged-in user | user, admin |
GET |
/api/v1/weather/history/user/:id |
Gets the query history of a specific user | admin |
| Method | Endpoint | Description | Role |
|---|---|---|---|
GET |
/api/v1/metrics |
Serves Prometheus metrics | Public |
| Method | Endpoint | Description | Role |
|---|---|---|---|
GET |
/api/v1/health |
Checks the health of the application | Public |
# Unit tests
npm run test
# E2E tests
npm run test:e2e
# Test coverage report
npm run test:covThe application implements a comprehensive logging and error handling strategy to ensure robustness and facilitate debugging.
-
LoggingInterceptor: This interceptor logs every incoming request and outgoing response. It captures the request method, URL, IP address, user agent, and the processing time. This provides a clear audit trail of all traffic. -
GlobalExceptionFilter: This global filter catches all unhandled exceptions throughout the application. It normalizes errors into a consistent JSON response format and logs them with appropriate severity levels:- HTTP Exceptions: Handled gracefully, returning standard HTTP status codes.
- Prisma Errors: Specific database errors (e.g., unique constraint violations, records not found) are caught and mapped to user-friendly error responses.
- 5xx Errors: Logged as
ERRORfor critical issues. - 4xx Errors: Logged as
WARNfor client-side issues.
The project has monitoring capabilities with Prometheus and Grafana.
- Prometheus: Accessible at
http://localhost:9090. It automatically scrapes thehttp://weather-app:3000/api/v1/metricsendpoint. - Grafana: Accessible at
http://localhost:8080. You can use a pre-configured dashboard by importing thegrafana-dashboard.jsonfile that comes with the project.
Below is a description of the variables that should be in the .env file.
| Variable | Description | Example Value |
|---|---|---|
NODE_ENV |
Application environment (development or production) |
development |
DATABASE_URL |
PostgreSQL database connection string | postgresql://admin123:password123@localhost:5432/weather_app?schema=public |
PORT |
The port the application will run on | 3000 |
JWT_SECRET |
Secret key used to sign JWT tokens | mysecretkeyforauthentication123456 |
JWT_SALT_ROUNDS |
Number of salt rounds for password hashing | 12 |
CORS_ORIGIN |
Allowed origins (comma-separated) | http://localhost:3000,http://localhost:3001 |
OPENWEATHER_API_KEY |
OpenWeatherMap API key | OpenWeatherMap-API-Key |
OPENWEATHER_GEOCODING_URL |
OpenWeatherMap Geocoding API URL | http://api.openweathermap.org/geo/1.0/direct |
OPENWEATHER_WEATHER_URL |
OpenWeatherMap Weather API URL | https://api.openweathermap.org/data/2.5/weather |
REDIS_HOST |
Redis server address | localhost |
REDIS_PORT |
Redis server port | 6379 |
REDIS_PASSWORD |
Redis password | redis_password_123 |
CACHE_TTL |
Cache data Time-To-Live in seconds | 300 |
CACHE_MAX_ITEMS |
Maximum number of items to keep in cache | 1000 |
This project is licensed under the MIT License. See the LICENSE file for more details.