A comprehensive, production-ready backend system for managing food-related data built with NestJS, Prisma, and PostgreSQL. The system provides secure API endpoints, OpenFoodFacts integration, user authentication via Keycloak, and is fully containerized for consistent deployment across environments.
- Modern Architecture: Built with NestJS, TypeScript, and Prisma ORM
- Food Data Management: Complete CRUD operations for food items
- OpenFoodFacts Integration: Automatic nutritional data retrieval from external API
- User Management: User profiles, preferences, and dietary restrictions
- Authentication & Authorization: Keycloak integration with JWT tokens and role-based access control
- Caching: Cache-backed caching for improved performance
- API Documentation: Comprehensive OpenAPI/Swagger documentation
- Testing: Unit, integration, and end-to-end tests with high coverage
- Monitoring: Health checks, metrics, and structured logging
- Security: Input validation, rate limiting, and security headers
- DevOps Ready: Docker containerization and CI/CD pipelines
- Features
- Prerequisites
- Quick Start
- Development Setup
- API Documentation
- Testing
- Deployment
- Architecture
- Contributing
- License
- Support
- Acknowledgments
- Node.js 24+ (specified in
.nvmrc) - npm 10+ (comes with Node.js 24)
- Docker and Docker Compose
- Git
This project uses Node.js 24. To ensure you're using the correct version:
Using nvm (recommended):
# Install nvm if you haven't already: https://github.com/nvm-sh/nvm
nvm install 24
nvm use 24The project includes an .nvmrc file that automatically sets the Node.js version when you run nvm use in the project directory.
git clone <repository-url>
cd foodmission-data-framework
npm installCreate environment files:
cp .env.example .env
cp .env.test.example .env.testConfigure your .env file:
# Database
DATABASE_URL="postgresql://postgres:password@localhost:5432/foodmission_db?schema=public"
DATABASE_URL_TEST="postgresql://postgres:password@localhost:5432/foodmission_test_db?schema=public"
# Cache
CACHE_URL="redis://localhost:6379"
# Keycloak
KEYCLOAK_BASE_URL="http://localhost:8080"
KEYCLOAK_AUTH_SERVER_URL="http://localhost:8080" # Optional: falls back to KEYCLOAK_BASE_URL if not provided
KEYCLOAK_REALM="foodmission"
KEYCLOAK_CLIENT_ID="foodmission-api"
KEYCLOAK_CLIENT_SECRET="your-keycloak-client-secret"
KEYCLOAK_SERVICE_CLIENT_ID="foodmission-service"
KEYCLOAK_SERVICE_CLIENT_SECRET="foodmission-service-secret"
# OpenFoodFacts
OPENFOODFACTS_API_URL="https://world.openfoodfacts.org"
# Application
NODE_ENV="development"
PORT=3000The application uses Keycloak for authentication. A pre-configured realm JSON file is included in the repository that contains all necessary configuration.
Keycloak should be running via Docker Compose. If not already started:
npm run docker:upKeycloak Admin Console will be available at http://localhost:8080
- Default username:
admin - Default password:
admin
- Access Keycloak Admin Console at
http://localhost:8080 - Log in with admin credentials (username:
admin, password:admin) - In the top-left corner, click the realm dropdown (shows "master" by default)
- Click "Create Realm" (or "Add realm")
- Click the "Browse..." button
- Navigate to and select:
keycloak/foodmission-realm.dev.jsonin your project directory - Click "Create" (or "Import")
The imported realm includes:
- Realm:
foodmission - Client:
foodmission-api & foodmission-service - Pre-configured users (passwords and JWT
subβ DBkeycloakIdalignment β see keycloak/README.md):developer,jane,mike,sarah,admin(plus service accounts as needed)
- Roles:
adminanduserroles for thefoodmission-apiclient
After seeding the database, JWT sub must match User.keycloakId for these users. Use the bundled realm file so user IDs stay stable.
- Switch to the
foodmissionrealm using the dropdown in the top-left corner - Navigate to Users β You should see at least:
admin,developer,jane,mike,sarah
- Navigate to Clients β You should see:
foodmission-apiclient
IMPORTANT: Never commit secrets to version control!
The client secret is configured in the realm JSON. You need to configure it in your .env file:
KEYCLOAK_CLIENT_SECRET="your-actual-client-secret-here"Security Best Practices:
- Use environment variables or a secret manager for sensitive values
- Never hardcode secrets in code or commit them to version control
- Use different secrets for development, staging, and production environments
- Rotate secrets regularly
To find your client secret:
- Go to Keycloak Admin Console β Clients β
foodmission-api - Go to the "Credentials" tab
- Copy the "Client Secret" value
- Set it in your
.envfile (which should be in.gitignore)
Test the setup with the pre-configured admin user:
Step 1: Get JWT Token
First, export your client secret as an environment variable (never include it directly in commands):
# Export the secret from your .env file
export KEYCLOAK_CLIENT_SECRET="your-actual-client-secret-here"Then use it in the curl command:
curl -X POST http://localhost:8080/realms/foodmission/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=foodmission-api" \
-d "client_secret=${KEYCLOAK_CLIENT_SECRET}" \
-d "username=admin" \
-d "password=admin123"Step 2: Test Protected Endpoint
Use the access_token from the response above:
# Replace YOUR_ACCESS_TOKEN with the actual token from Step 1
curl http://localhost:3000/api/v1/auth/admin-only \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Security Best Practices:
- β Always use environment variables for secrets
- β Never include secrets directly in command-line arguments
- β Never commit secrets to version control
- β Use secure credential stores in production environments
# Start database and cache services
npm run docker:up
# Generate Prisma client
npm run db:generate
# Run database migrations
npm run db:migrate:deploy
# Seed the database with initial data
npm run db:seed# Development mode with hot reload
npm run start:dev
# The API will be available at http://localhost:3000
# Swagger documentation at http://localhost:3000/api/docsFor the best development experience, use the provided DevContainer:
- Open the project in VSCode
- Install the "Dev Containers" extension
- Press
Ctrl+Shift+Pand select "Dev Containers: Reopen in Container" - The container will automatically set up the development environment
# Ensure you're using the correct Node.js version
# If using nvm, the .nvmrc file will automatically set Node.js 24
nvm use
# Verify version
node --version # Should be v24.x.x
# Install dependencies
npm install
# Set up development environment
npm run dev:setup
# Start development services
npm run docker:up
# Start the application
npm run start:devnpm run start:dev- Start in development mode with hot reloadnpm run start:debug- Start in debug modenpm run dev:setup- Set up development environmentnpm run dev:reset- Reset development database
npm run db:generate- Generate Prisma clientnpm run db:migrate- Create and apply new migrationnpm run db:migrate:deploy- Apply existing migrationsnpm run db:migrate:reset- Reset database and apply all migrationsnpm run db:studio- Open Prisma Studio (database GUI)npm run db:seed- Seed database with initial datanpm run db:backup- Create database backupnpm run db:restore- Restore database from backup
npm run test- Run unit testsnpm run test:unit- Run unit tests onlynpm run test:integration- Run integration testsnpm run test:e2e- Run end-to-end testsnpm run test:cov- Run tests with coverage reportnpm run ci:test- Run all tests in CI mode
npm run build- Build the applicationnpm run start:prod- Start in production modenpm run docker:up- Start Docker servicesnpm run docker:down- Stop Docker services
npm run lint- Run ESLintnpm run format- Format code with Prettier
For local analytics testing, anonymization details, and batch publish workflow, see:
docs/analytics_instructions:.mddocs/Data anonymization/
- Interactive UI (Swagger): http://localhost:3000/api/docs when the app is running (replace host in production). Use Try it out for request/response examples.
- Generated files (commit after API changes):
docs/openapi.yamlanddocs/openapi.jsonβ regenerate withnpm run docs:generate.
The project includes comprehensive testing at multiple levels:
- Unit Tests: Test individual components in isolation
- Integration Tests: Test module interactions and database operations
- End-to-End Tests: Test complete API workflows
# Run all tests
npm test
# Run specific test types
npm run test:unit
npm run test:integration
npm run test:e2e
# Run tests with coverage
npm run test:cov
# Run tests in watch mode
npm run test:watch
# Run tests in CI mode
npm run ci:testThe project maintains high test coverage:
- Unit Tests: 85%+ coverage
- Integration Tests: Critical paths covered
- E2E Tests: Main user workflows covered
Tests are located in:
src/**/*.spec.ts- Unit teststest/**/*.integration.spec.ts- Integration teststest/**/*.e2e-spec.ts- End-to-end tests
# Build the Docker image
docker build -t foodmission-data-framework .
# Run with Docker Compose
docker-compose up -dThe docker-compose.yml file includes:
- PostgreSQL: Database server with test database initialization
- Cache: Ephemeral cache store (Valkey)
- Keycloak: Authentication and authorization server
For production deployments, create a custom docker-compose file or use container orchestration platforms with appropriate environment configurations.
Infrastructure and deployment configuration is managed separately in the foodmission-infra repository. This includes Kubernetes manifests, Helm charts, and environment-specific configurations for staging and production.
The project uses GitHub Actions for automated CI/CD:
- Continuous Integration: Automated testing, linting, and security scanning
- Quality Gates: Code coverage and security requirements
- Code Quality: ESLint, Prettier, TypeScript compilation
- Security: Dependency scanning, SAST analysis
- Testing: Unit, integration, and e2e tests
- Build: Docker image creation and push to GitHub Container Registry
- Local database and cache
- Hot reloading enabled
- Debug logging
- Mock external services
- Shared database instance
- Production-like configuration
- Integration with external services
- Performance monitoring
- High-availability database cluster
- Shared cache cluster for rate limiting and caching
- Full monitoring and alerting
- Security hardening
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Client Apps β β Load Balancer β β API Gateway β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β
βββββββββββββββββββββββββΌββββββββββββββββββββββββ
β
βββββββββββββββββββββββββΌββββββββββββββββββββββββ
β NestJS Application β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β Controllers Layer β β
β βββββββββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β Services Layer β β
β βββββββββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β Repository Layer β β
β βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β β β
βββββΌβββββ ββββββββΌβββββββ ββββββββΌβββββββ
βPostgreSQLβ β Cache β β Keycloak β
βDatabase β β (Valkey) β β Auth β
ββββββββββββ βββββββββββββββ βββββββββββββββ
src/
βββ auth/ # Authentication & authorization
βββ cache/ # Caching services and decorators
βββ common/ # Shared utilities and exceptions
βββ database/ # Database configuration and services
βββ food/ # Food management module
βββ health/ # Health checks and monitoring
βββ monitoring/ # Metrics and performance monitoring
βββ security/ # Security services and middleware
βββ user/ # User management module
- Repository Pattern: Data access abstraction
- Dependency Injection: Loose coupling and testability
- Decorator Pattern: Cross-cutting concerns (caching, validation)
- Strategy Pattern: Multiple authentication strategies
- Observer Pattern: Event-driven architecture
-- Core entities
Food (id, name, description, barcode, openFoodFactsId, createdBy)
User (id, keycloakId, email, firstName, lastName)
UserPreferences (id, userId, dietaryRestrictions, allergies, preferredCategories)We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Run the test suite:
npm test - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- TypeScript: Strict mode enabled
- ESLint: Airbnb configuration with custom rules
- Prettier: Consistent code formatting
- Conventional Commits: Standardized commit messages
- Test Coverage: Minimum 80% coverage required
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.
- Documentation: Full documentation
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- NestJS - Progressive Node.js framework
- Prisma - Next-generation ORM
- OpenFoodFacts - Open food database
- Keycloak - Identity and access management
- RIVM - Food Category nutritional data