Norish is a real-time, household-first recipe app for planning meals, sharing groceries, and cooking together.
- Norish
- Nora
The vision for Norish is a shared recipe app built for friends, families, and households that want to share a recipe catalogue.
The name comes from Nora (our dog) + dish. Coincidentally, it also sounds like "nourish".
Norish started because we wanted a cooking app that felt intuitive and easy to use. The existing apps we tested sadly did not meet our requirements in ease of use and aestethics.
Norish is intentionally minimal. It focuses on practical day-to-day use.
- Easy recipe import from URL, with AI fallback if configured.
- Video recipe import from YouTube Shorts, Instagram Reels, TikTok, and more (requires AI provider).
- Image recipe import from screenshots/photos of recipes (requires AI provider).
- Nutritional information generation (requires AI provider).
- Allergy detection and warnings for recipe ingredients (detection requires AI provider).
- Unit conversion metric <-> US (requires AI provider).
- Recurring groceries via NLP or manual setup.
- Real-time sync of recipes, groceries, and meal planning data.
- Households with shared groceries and planning.
- CalDAV sync for calendar integration.
- Mobile-first design with light/dark mode support.
- Authentication options: OIDC, OAuth providers, and first-time password auth fallback.
- Admin settings UI for runtime configuration.
- Permission policies for recipe visibility/edit/delete scopes.
- Internationalization (i18n) currently supporting EN, NL, DE, FR, ES and RU
Note: AI feature speed can vary by provider, model, and region.
services:
norish:
image: norishapp/norish:latest
container_name: norish-app
restart: always
ports:
- "3000:3000"
user: "1000:1000"
volumes:
- norish_data:/app/uploads
environment:
AUTH_URL: http://norish.example.com
DATABASE_URL: postgres://postgres:norish@db:5432/norish
MASTER_KEY: <32-byte-base64-key> # openssl rand -base64 32
CHROME_WS_ENDPOINT: ws://chrome-headless:3000
REDIS_URL: redis://redis:6379
# Optional
# NEXT_PUBLIC_LOG_LEVEL: info
# TRUSTED_ORIGINS: http://192.168.1.100:3000,https://norish.example.com
# YT_DLP_BIN_DIR: /app/bin
# First-user auth setup (choose one)
# PASSWORD_AUTH_ENABLED=false
# OIDC_NAME: NoraId
# OIDC_ISSUER: https://auth.example.com
# OIDC_CLIENT_ID: <client-id>
# OIDC_CLIENT_SECRET: <client-secret>
# OIDC_WELLKNOWN: https://auth.example.com/.well-known/openid-configuration
# GITHUB_CLIENT_ID: <github-client-id>
# GITHUB_CLIENT_SECRET: <github-client-secret>
# GOOGLE_CLIENT_ID: <google-client-id>
# GOOGLE_CLIENT_SECRET: <google-client-secret>
healthcheck:
test:
test:
[
"CMD-SHELL",
'node -e "require(''http'').get(''http://localhost:3000/api/health'', r => process.exit(r.statusCode===200?0:1))"',
]
interval: 1m
timeout: 15s
retries: 3
start_period: 1m
depends_on:
- db
- redis
db:
image: postgres:17-alpine
container_name: norish-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: norish
POSTGRES_DB: norish
volumes:
- db_data:/var/lib/postgresql/data
chrome-headless:
image: zenika/alpine-chrome:latest
container_name: chrome-headless
restart: unless-stopped
command:
- "--no-sandbox"
- "--disable-gpu"
- "--disable-dev-shm-usage"
- "--remote-debugging-address=0.0.0.0"
- "--remote-debugging-port=3000"
- "--headless"
redis:
image: redis:8.4.0
container_name: norish-redis
restart: unless-stopped
volumes:
- redis_data:/data
volumes:
db_data:
norish_data:
redis_data:The first user to sign in becomes server owner + server admin. After first sign-in:
- User registration is disabled automatically.
- Ongoing server settings are managed in
Settings -> Admin.
Server owners/admins can manage:
- Registration policy.
- Permission policies for recipe view/edit/delete.
- Auth providers (OIDC, GitHub, Google).
- OIDC claim mapping for admin role assignment + household auto-join.
- Content detection settings (units, content indicators, recurrence config).
- AI + video processing settings.
- System scheduler and server restart actions.
env-config-server.ts is the source of truth for runtime env vars.
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgres://user:pass@db:5432/norish |
MASTER_KEY |
32+ character key for encryption derivation | openssl rand -base64 32 |
| Variable | Description | Typical value |
|---|---|---|
AUTH_URL |
Public URL used for callbacks and links | https://norish.example.com |
CHROME_WS_ENDPOINT |
Playwright CDP WebSocket endpoint for scraping | ws://chrome-headless:3000 |
REDIS_URL |
Redis connection URL for events and jobs | redis://redis:6379 |
| Variable | Description | Default |
|---|---|---|
NODE_ENV |
Runtime environment | development |
HOST |
Server bind address | 0.0.0.0 |
PORT |
Server port | 3000 |
AUTH_URL |
Public URL for auth callbacks and links | http://localhost:3000 |
TRUSTED_ORIGINS |
Comma-separated additional trusted origins | (empty) |
UPLOADS_DIR |
Upload storage directory | ./uploads |
CHROME_WS_ENDPOINT |
Playwright CDP WebSocket endpoint | ws://chrome-headless:3000 |
REDIS_URL |
Redis connection URL | redis://localhost:6379 |
ENABLE_REGISTRATION |
Allow new-user registration | false |
AI_ENABLED |
Enable AI features globally | false |
Configure one provider for initial sign-in; after that, use Settings -> Admin.
Provider callback URLs:
| Provider | Callback URL |
|---|---|
| OIDC | https://example.norish-domain.com/api/auth/oauth2/callback/oidc |
| GitHub | https://example.norish-domain.com/api/auth/callback/github |
https://example.norish-domain.com/api/auth/callback/google |
| Variable | Description | Default |
|---|---|---|
PASSWORD_AUTH_ENABLED |
Enable email/password auth bootstrap | Auto |
OIDC_NAME |
Display name for OIDC provider | (empty) |
OIDC_ISSUER |
OIDC issuer URL | (empty) |
OIDC_CLIENT_ID |
OIDC client id | (empty) |
OIDC_CLIENT_SECRET |
OIDC client secret | (empty) |
OIDC_WELLKNOWN |
OIDC well-known URL (derived from issuer if omitted) | Derived |
GITHUB_CLIENT_ID |
GitHub OAuth client id | (empty) |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret | (empty) |
GOOGLE_CLIENT_ID |
Google OAuth client id | (empty) |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret | (empty) |
These are only used when claim mapping is enabled.
| Variable | Description | Default |
|---|---|---|
OIDC_CLAIM_MAPPING_ENABLED |
Enable claim-based role and household assignment | false |
OIDC_SCOPES |
Additional OIDC scopes (comma-separated) | (empty) |
OIDC_GROUPS_CLAIM |
Claim name containing group memberships | groups |
OIDC_ADMIN_GROUP |
Group name that grants server admin role | norish_admin |
OIDC_HOUSEHOLD_GROUP_PREFIX |
Prefix for household auto-join groups | norish_household_ |
| Variable | Description | Default |
|---|---|---|
AI_PROVIDER |
AI provider | openai |
AI_ENDPOINT |
Custom provider endpoint | (empty) |
AI_MODEL |
Default model | gpt-5-mini |
AI_API_KEY |
API key for provider | (empty) |
AI_TEMPERATURE |
Generation temperature | 1.0 |
AI_MAX_TOKENS |
Maximum tokens for model responses | 10000 |
| Variable | Description | Default |
|---|---|---|
VIDEO_PARSING_ENABLED |
Enable video parsing pipeline | false |
VIDEO_MAX_LENGTH_SECONDS |
Maximum accepted video length | 120 |
YT_DLP_VERSION |
yt-dlp version used by downloader | 2025.11.12 |
YT_DLP_BIN_DIR |
Folder containing yt-dlp binary | env-dependent |
TRANSCRIPTION_PROVIDER |
Transcription provider | disabled |
TRANSCRIPTION_ENDPOINT |
Transcription endpoint (local/custom providers) | (empty) |
TRANSCRIPTION_API_KEY |
Transcription API key | (empty) |
TRANSCRIPTION_MODEL |
Transcription model | whisper-1 |
| Variable | Description | Default |
|---|---|---|
UNITS_JSON |
Override units dictionary | (empty) |
CONTENT_INDICATORS |
Override recipe-content indicator configuration | (empty) |
CONTENT_INGREDIENTS |
Override ingredient-content configuration | (empty) |
| Variable | Description | Default |
|---|---|---|
SCHEDULER_CLEANUP_MONTHS |
Cleanup retention period in months | 3 |
MAX_AVATAR_FILE_SIZE |
Max avatar upload size (bytes) | 5242880 |
MAX_IMAGE_FILE_SIZE |
Max image upload size (bytes) | 10485760 |
MAX_VIDEO_FILE_SIZE |
Max video upload size (bytes) | 104857600 |
| Variable | Description | Default |
|---|---|---|
DEFAULT_LOCALE |
Instance default locale | en |
ENABLED_LOCALES |
Comma-separated list of enabled locales | (all enabled) |
# Clone the repository
git clone https://github.com/mikeve97/norish.git
cd norish
# Install dependencies
pnpm install
# Create your environment file
cp .env.example .env.local
# Start required services (for example via Docker)
# docker run -d --name norish-db -e POSTGRES_PASSWORD=norish -e POSTGRES_DB=norish -p 5432:5432 postgres:17-alpine
# docker run -d --name norish-redis -p 6379:6379 redis:7-alpine
# Run the app
pnpm run dev| Command | Description |
|---|---|
pnpm run dev |
Start development server with hot reload |
pnpm run build |
Full production build (Next.js + server + service worker) |
pnpm run test |
Run tests in watch mode |
pnpm run test:run |
Run tests once |
pnpm run test:coverage |
Run tests with coverage report |
pnpm run lint |
Lint and auto-fix issues |
pnpm run lint:check |
Lint TypeScript files |
pnpm run format |
Format files with Prettier |
pnpm run format:check |
Check formatting without changing files |
- Next.js 16
- Tailwind CSS 4
- HeroUI
- Framer Motion
- TanStack Query
- Node.js custom server
- tRPC
- Better Auth
- Pino
- Redis
- BullMQ
- PostgreSQL
- Drizzle ORM
- OpenAI SDK
- Playwright
- yt-dlp
- Sharp
- FFmpeg
- Vitest
Norish is licensed under AGPL-3.0.
This list is not limited to the below but the ones I know:
Last but not least, a picture of our lovely dog Nora:

