This repository contains a collection of example applications for the Nais platform. The main example is a Quotes application, where users can view and create quotes. Each service demonstrates a specific technology stack and integration pattern commonly used in modern cloud-native applications on Nais.
Another key purpose of these examples is to showcase the observability features available in the Nais Platform, based on OpenTelemetry. This includes:
- Logs collected in Loki
- Metrics collected in Prometheus
- Traces collected in Tempo
All observability data is available and visualized in Grafana, making it easy to monitor, debug, and understand your applications running on Nais.
The purpose of this repository is to help developers understand how to:
- Build and deploy frontend, backend, and load generation services on Nais
- Integrate with managed databases (PostgreSQL)
- Use modern frameworks and best practices for cloud applications
This repository consists of the following services:
| Service | Tech Stack | Purpose & Description |
|---|---|---|
| quotes-frontend | Next.js, React, Tailwind CSS | The web frontend for the Quotes app, allowing users to view and submit quotes. Includes analytics dashboard. |
| quotes-backend | Kotlin, Ktor, PostgreSQL | The backend API for the Quotes app, handling quote storage and retrieval. |
| quotes-analytics | .NET 8, ASP.NET Core | Analytics service that processes quote data from the backend, providing insights and custom OpenTelemetry metrics. |
| quotes-loadgen | Go (Golang) | A load generator for simulating traffic and testing the Quotes application. |
- Users interact with the quotes-frontend (Next.js) to view and submit quotes
- Frontend communicates with quotes-backend (Kotlin/Ktor) for quote storage/retrieval
- Analytics service (.NET) processes quotes from the backend to provide:
- Word count analysis
- Sentiment scoring
- Quote categorization
- Custom OpenTelemetry metrics
- Frontend displays analytics via a dedicated
/analyticspage - Load generator (Go) simulates traffic to test the entire system
- All services send observability data (traces, metrics, logs) to the OTEL stack
# Start all services
docker-compose up -d
# Access the application
open http://localhost:3000 # Frontend (quotes + analytics)
open http://localhost:8080 # Backend API
open http://localhost:8081 # Analytics API
open http://localhost:3000/analytics # Analytics Dashboard
open http://localhost:3000 # Grafana (logs, metrics, traces)For more details on each service, see the README in the respective subdirectory:
- quotes-frontend/README.md
- quotes-backend/README.md
- quotes-analytics/README.md
- quotes-loadgen/README.md
A high-level overview of the Quotes application and its dependencies:
---
config:
flowchart:
defaultRenderer: elk
---
graph LR
subgraph Browser
A(User)
end
subgraph Frontend
B(Next.js)
end
subgraph Backend
C(Ktor API)
D[(PostgreSQL)]
end
subgraph Analytics
F(.NET 8)
end
subgraph Loadgen
E(Go)
end
subgraph Observability
G[Grafana/OTEL]
end
A --> B
B --> C
B --> F
C --> D
F --> C
E --> B
E --> F
B -.-> G
C -.-> G
F -.-> G
E -.-> G
This project demonstrates Unleash feature flagging on the NAIS platform. Unleash lets you toggle features on and off without redeploying.
Each service has an Unleash API token defined in .nais/unleash.yaml. When deployed, the NAIS Unleash operator provisions a client token and stores it as a Kubernetes secret. The app reads the secret via envFrom in .nais/app.yaml:
# .nais/app.yaml
envFrom:
- secret: quotes-backend-unleash-api-tokenThis provides the environment variables UNLEASH_SERVER_API_URL, UNLEASH_SERVER_API_TOKEN, and UNLEASH_SERVER_API_ENVIRONMENT to the application at runtime.
| Flag | Service | Effect |
|---|---|---|
quotes.submit |
quotes-backend, quotes-frontend | Controls whether users can submit new quotes. When disabled, the backend returns 403 and the frontend hides/disables the submit button. |
quotes.errors |
quotes-backend | Enables simulated error injection (10% error rate on GET/POST endpoints). Default: disabled. Turn on to generate errors visible in dashboards and alerts. |
-
Create the toggle in the Unleash UI (or on NAIS at your team's Unleash instance)
-
Check the flag in code:
Kotlin (backend):
if (FeatureFlags.isEnabled("my.new.flag")) { // feature code }
TypeScript (frontend, server-side):
import { isEnabled } from '@/utils/unleash'; const enabled = isEnabled('my.new.flag');
-
Register the flag name in
FeatureFlags.kt(backend) orunleash.ts(frontend) so it appears in the/api/featuresendpoint
Unleash runs locally via docker-compose on port 4242. The admin UI is at http://localhost:4242. Log in with the default credentials configured in docker-compose.yaml (username admin, password unleash).
To create the quotes.submit toggle locally:
- Start infrastructure:
mise run infra:up - Open http://localhost:4242
- Create feature flags named
quotes.submitandquotes.errorsin thedevelopmentenvironment - Enable or disable them to see the effect in the running application
The local client token default:development.client-token is pre-configured in both .mise.toml (for mise run dev) and docker-compose.yaml.
When Unleash is unavailable (no env vars set, or server unreachable), feature flags fall back to their configured default values. These defaults are defined per flag in the backend/frontend and may be either enabled or disabled. This means:
- Tests can run without Unleash, using the configured default values for each flag
- A misconfigured Unleash connection won't break the application; features will follow their safe defaults
The code in this repository is licensed under the MIT license. See LICENSE for more information.