A NestJS backend for APL Dashi, providing cookie-based authentication, user lookup, threaded posts with file attachments, health checks, and local observability.
PostgreSQL is the source of truth through Prisma, Redis stores authenticated session data, and an S3-compatible object store keeps uploaded post assets. The local Docker Compose environment uses MinIO for object storage and Loki, Fluent Bit, and Grafana for log collection and visualization.
flowchart LR
Client[Client / Web App]
API[NestJS API]
Postgres[(PostgreSQL)]
Redis[(Redis)]
Storage[(S3-compatible Storage / MinIO)]
Docs[Swagger + Scalar]
FluentBit[Fluent Bit]
Loki[(Loki)]
Grafana[Grafana]
Client -->|HTTP / cookies| API
API -->|Prisma reads and writes| Postgres
API -->|Session data| Redis
API -->|Post attachments| Storage
API -->|OpenAPI document| Docs
API -->|Container logs| FluentBit
FluentBit --> Loki
Grafana -->|Query logs| Loki
| Technology | Purpose |
|---|---|
| NestJS 11 | HTTP API and application modules |
| TypeScript | Application language |
| Prisma 7 | PostgreSQL schema, migrations, and typed client |
| PostgreSQL | Primary database and source of truth |
| Redis | Session storage |
| MinIO / S3 | S3-compatible post attachment storage |
| Swagger + Scalar | API documentation and OpenAPI reference |
| Nest Terminus | Health checks for memory, disk, and database |
| Vitest | Unit tests |
| Biome | Linting and formatting |
| Docker Compose | Local infrastructure and container orchestration |
| Loki + Fluent Bit | Container log collection |
| Grafana | Local log visualization |
| pnpm | Package management |
.
|-- src/
| |-- app/
| | |-- asset/ # Uploaded asset metadata service
| | |-- auth/ # Signup, signin, signout, session guard and strategy
| | |-- database/ # Prisma and Redis services
| | |-- filters/ # Global HTTP exception filter
| | |-- health/ # Terminus health endpoint
| | |-- post/ # Post creation, pagination and thread retrieval
| | |-- upload/ # Upload service, provider factory and S3 provider
| | `-- user/ # User lookup endpoint and service
| |
| |-- config/ # Application and storage configuration
| |-- generated/ # Generated Prisma client
| |-- utils/ # Shared helpers and validators
| `-- main.ts # Nest bootstrap, CORS, Swagger and Scalar setup
|
|-- prisma/
| `-- schema.prisma # Database models, relations, enums and indexes
|
|-- metrics/ # Loki, Fluent Bit and Grafana provisioning
|-- Dockerfile # Production container build
|-- docker-compose.yaml # API, database, cache, storage and observability stack
`-- entrypoint.sh # Production startup script
| Path | Responsibility |
|---|---|
src/app/auth |
Cookie authentication, session validation and guard |
src/app/post |
Post creation, attachment linking and thread reads |
src/app/upload |
S3-compatible upload and deletion operations |
src/app/database |
Prisma and Redis integration |
src/app/health |
Runtime health checks |
prisma/schema.prisma |
Database schema and relations |
metrics/ |
Local logging and Grafana provisioning |
Make sure the following tools are installed before running the project:
| Tool | Version / Notes |
|---|---|
| Node.js | 22 |
| pnpm | 10.29.2 |
| Docker | Docker Engine or Docker Desktop |
| Docker Compose | Compose v2 |
| PostgreSQL | Required for host-based local development |
| Redis | Required for host-based local development |
| MinIO / S3 | Required for attachment uploads in development |
pnpm installStart from .env.example:
cp .env.example .env
cp .env.example .env.productionUse .env for host-based development and .env.production for Docker Compose.
When running with Docker Compose, containers should use Docker service hostnames instead of localhost. For example:
DATABASE_URL="postgresql://postgres:root@postgres:5432/root?schema=public"
REDIS_HOST="redis"
S3_ENDPOINT="http://minio:9000"
S3_PUBLIC_URL="http://localhost:9000/uploads"Set real values for secrets before starting the application:
SESSION_KEY
REDIS_PASSWORD
MINIO_ROOT_PASSWORD
S3_SECRET_KEY
The most relevant application variables are:
| Variable | Purpose |
|---|---|
PORT |
API port, defaults to 5400 |
DATABASE_URL |
PostgreSQL connection string |
REDIS_HOST, REDIS_PORT, REDIS_PASSWORD |
Redis session store connection |
SESSION_COOKIE, SESSION_EXPIRES_IN, SESSION_KEY |
Cookie and session configuration |
CLIENT_URL |
CORS allowed origin |
S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET, S3_REGION, S3_PUBLIC_URL |
S3-compatible storage configuration |
MINIO_ROOT_USER, MINIO_ROOT_PASSWORD |
Local MinIO credentials |
pnpm db:generate
pnpm db:migrate:devpnpm start:devThe API is served with the global /api prefix.
docker compose --env-file .env.production up --buildThe API container uses entrypoint.sh to run production migrations and then start the compiled server:
pnpm run db:migrate:deploy
pnpm run start:prodFor faster development cycles, run the supporting services with Docker and start the NestJS API directly on the host.
Start infrastructure:
docker compose --env-file .env.production up -d postgres redis minio minio-initGenerate the Prisma client and apply development migrations:
pnpm db:generate
pnpm db:migrate:devStart the API in watch mode:
pnpm start:dev| Service | URL |
|---|---|
| API | http://localhost:5400/api |
| Swagger UI | http://localhost:5400/api/docs |
| Scalar Reference | http://localhost:5400/api/reference |
| Health Check | http://localhost:5400/api/health |
| PostgreSQL | localhost:5432 |
| Redis | localhost:6379 |
| MinIO API | http://localhost:9000 |
| MinIO Console | http://localhost:9001 |
| Loki | http://localhost:3100 |
| Grafana | http://localhost:3001 |
Grafana uses admin / admin by default in the local Compose stack.
| Area | Capability |
|---|---|
| Authentication | Signup, signin, signout, and authenticated session lookup |
| Sessions | HTTP-only cookie sessions backed by Redis |
| Users | Authenticated user lookup by ID |
| Posts | Authenticated post creation, pagination, item lookup and threaded replies |
| Attachments | Multipart uploads stored through the S3 provider and linked as assets |
| Health | Memory, RSS, disk and database health checks |
| Documentation | Swagger UI and Scalar API reference generated from the Nest application |
| Observability | Docker log forwarding through Fluent Bit into Loki, visualized with Grafana |
Global API prefix: /api
| Area | Method | Path | Notes |
|---|---|---|---|
| Authentication | POST |
/api/auth/signup |
Creates a user and sets the auth cookie |
| Authentication | POST |
/api/auth/signin |
Authenticates a user and sets the auth cookie |
| Authentication | POST |
/api/auth/signout |
Requires an authenticated session |
| Authentication | GET |
/api/auth |
Returns the authenticated session |
| Users | GET |
/api/users/show/:userId |
Requires an authenticated session |
| Posts | POST |
/api/posts/create |
Requires authentication and accepts multipart attachments[] |
| Posts | GET |
/api/posts/paginate?cursor=<id>&limit=<n> |
Requires an authenticated session |
| Posts | GET |
/api/posts/item/:id |
Requires an authenticated session and returns item plus replies |
| Health | GET |
/api/health |
Runtime health check |
Prisma schema source: prisma/schema.prisma
| Model | Responsibility |
|---|---|
User |
Account identity with email, username, password, emailVerified and timestamps |
UserPreferences |
User profile preferences such as theme and avatar |
Post |
Main domain entity with content, tags, coordinates, status, author and optional parent post |
Asset |
Uploaded file metadata with URL, storage key, provider and optional post relation |
Relationships:
User 1:N Post
User 1:N UserPreferences
Post 1:N Asset
Post 1:N Post replies through parentId
Enums:
| Enum | Values |
|---|---|
Status |
SOLVED, IN_PROGRESS, PENDING |
AssetProvider |
LOCAL, AWS_S3, CLOUDINARY |
Indexes and constraints:
| Target | Type |
|---|---|
User.email |
Unique constraint |
User.username |
Unique constraint |
Post.parentId |
Index for threaded reply queries |
Creating a post with attachments follows this flow:
POST /api/posts/create
|
v
Upload each attachment to S3-compatible storage
|
v
Create the post in PostgreSQL through Prisma
|
v
Link uploaded assets to the created post
If post creation fails after one or more files were uploaded, the API attempts to delete the uploaded assets before returning the error response.
Start in development mode:
pnpm start:devBuild the application:
pnpm buildRun the compiled server:
pnpm start:prodRun tests:
pnpm testRun test coverage:
pnpm test:covRun Biome checks:
pnpm lintFormat files:
pnpm formatOpen Prisma Studio:
pnpm studioRun development migrations:
pnpm db:migrate:devRun deploy migrations:
pnpm db:migrate:deployRegenerate the Prisma client:
pnpm db:generateStart the full Docker stack:
docker compose --env-file .env.production up --buildStart the stack in the background:
docker compose --env-file .env.production up -dStop the stack:
docker compose --env-file .env.production downView container logs:
docker compose --env-file .env.production logs -fThis repository is primarily a backend API for experimenting with and supporting:
| Goal | Description |
|---|---|
| Cookie sessions | HTTP-only authentication with Redis-backed session state |
| Threaded posts | Parent/child post modeling for replies and discussions |
| Attachment uploads | S3-compatible asset storage with database metadata |
| Prisma modeling | Typed PostgreSQL access and migrations with Prisma 7 |
| Local infrastructure | Reproducible PostgreSQL, Redis, MinIO and observability services through Docker Compose |
| API documentation | Runtime Swagger and Scalar references generated from the application |
The main architectural principle is simple:
PostgreSQL owns the application data. Redis owns temporary session state. S3-compatible storage owns uploaded files.