|
|
||
|---|---|---|
| .forgejo/workflows | ||
| .vscode | ||
| .windsurf/rules | ||
| adapters/express | ||
| docs | ||
| drizzle/migrations | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc.js | ||
| deploy-portainer.sh | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| eslint.config.js | ||
| field-requirements.md | ||
| package-lock.json | ||
| package.json | ||
| qwik.env.d.ts | ||
| README.md | ||
| tailwind.config.ts | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
Qwik City App ⚡️
Project Structure
This project is using Qwik with QwikCity. QwikCity is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more.
Inside your project, you'll see the following directory structure:
├── public/
│ └── ...
└── src/
├── components/
│ └── ...
└── routes/
└── ...
-
src/routes: Provides the directory-based routing, which can include a hierarchy oflayout.tsxlayout files, and anindex.tsxfile as the page. Additionally,index.tsfiles are endpoints. Please see the routing docs for more info. -
src/components: Recommended directory for components. -
public: Any static assets, like images, can be placed in the public directory. Please see the Vite public directory for more info.
Add Integrations and deployment
Use the npm run qwik add command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the Static Site Generator (SSG).
npm run qwik add # or `yarn qwik add`
Development
Development mode uses Vite's development server. The dev command will server-side render (SSR) the output during development.
npm start # or `yarn start`
Note: during dev mode, Vite may request a significant number of
.jsfiles. This does not represent a Qwik production build.
Preview
The preview command will create a production build of the client modules, a production build of src/entry.preview.tsx, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server.
npm run preview # or `yarn preview`
Production
The production build will generate client and server modules by running both client and server build commands. The build command will use Typescript to run a type check on the source code.
npm run build # or `yarn build`
Setup & Local Development
Prerequisites
- Node.js 18.17+ or 20.3+ (see
package.jsonengines) - PostgreSQL (locally or remote). Version 14+ recommended.
- A Postgres connection string for
DATABASE_URL.
1) Install dependencies
npm install
2) Configure environment
Create a .env file in the project root with your database URL. Example:
# .env
DATABASE_URL=postgres://postgres:postgres@localhost:5432/music_lookup
Notes:
src/lib/db/client.tspreloadsdotenv, so.envwill be read automatically in dev/test.- Ensure your database exists and is reachable by the connection string.
3) Run database migrations
Apply all pending Drizzle migrations to your database:
npm run db:migrate
If you change the schema in src/lib/db/schema.ts, generate a new migration first:
npm run db:generate
npm run db:migrate
Optionally seed development data:
npm run db:seed
4) Start the dev server
npm run dev # SSR dev server
# or
npm start # opens browser, SSR mode
Open the app at the URL printed by Vite (usually http://localhost:5173).
Data Model & Requirements
- The canonical requirements for the Pieces feature live in
field-requirements.md. - Key fields (Pieces):
title(required),hymnTune,composer,lastPlayed,description(Notes). - "Type" is managed via Tags (
/api/tags) withkind=type. The UI should suggest existing values but allow creating new ones. - Search is full‑text across title, hymn tune, composer, notes, tags, and book titles (locations).
Common Commands
- Lint:
npm run lint - Format check:
npm run fmt.check(or fix:npm run fmt) - Type check:
npm run build.types - Unit tests:
npm test(watch:npm run test:watch, coverage:npm run coverage) - Build preview server:
npm run preview
Troubleshooting
- "DATABASE_URL is not set" or connection errors: verify
.envexists, Postgres is running, and the database name/user/password are correct. - Migrations fail: ensure the DB exists and the user has permissions; check
drizzle/migrations/for the latest SQL and re-runnpm run db:migrate. - UI issues after schema changes: confirm you generated and applied new migrations (
npm run db:generate && npm run db:migrate) and optionally reseed (npm run db:seed).
References
- Qwik docs: https://qwik.dev/
- Qwik City routing: https://qwik.dev/qwikcity/routing/overview/
- Drizzle ORM: https://orm.drizzle.team/
Quick Start
# 1) Install deps
npm install
# 2) Create .env with your Postgres connection string
printf "DATABASE_URL=postgres://postgres:postgres@localhost:5432/music_lookup\n" > .env
# 3) Apply migrations (and optionally seed)
npm run db:migrate
npm run db:seed # optional
# 4) Start dev server
npm run dev
Then open http://localhost:5173
Resetting the DB for local development
If your local DB has drifted or migrations failed, use one of the following approaches.
-
Option B (one command): Drop and recreate the whole database via script
Uses
scripts/reset-db.mjsto terminate connections, drop, recreate, then run migrations (and optional seed).npm run db:reset # drop + recreate + migrate npm run db:reset:seed # drop + recreate + migrate + seed npm run db:reset:no-migrate # drop + recreate only -
Option A: Reset the
publicschema (keeps the same database)DANGER: This drops all tables in the database schema referenced by
DATABASE_URL.# Uses the DATABASE_URL from your .env export $(grep -v '^#' .env | xargs) # loads DATABASE_URL into the shell (bash/zsh) psql "$DATABASE_URL" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" # Re-apply schema npm run db:migrate npm run db:seed # optional -
Option B: Drop and recreate the whole database
Replace
music_lookupwith your actual DB name fromDATABASE_URL.# Terminate connections, drop, recreate psql -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'music_lookup';" dropdb music_lookup || true createdb music_lookup # Re-apply schema npm run db:migrate npm run db:seed # optional -
Option C: Create a fresh DB and point
.envto itcreatedb music_lookup_dev # then update DATABASE_URL in .env to use music_lookup_dev and run migrations/seed npm run db:migrate
Notes
- If you see errors like "Failed query" or missing columns on pages like
/pieces/, confirm that all migrations have been applied (e.g.,0001,0002,0003). - The project auto-loads
.envin dev/test viasrc/lib/db/client.ts. - After a reset, always run
npm run db:migrate(andnpm run db:seedif you want demo data).
CI/CD and Production Deployment
This repo ships with a Forgejo Actions workflow and Docker/Compose config to deploy via Portainer.
Docker images
Dockerfileis multi-stage:- dev target runs
npm run dev.hoston port5173. - prod image builds the app and serves it with a Node/Express server on port
3000. - On container start in prod, it runs database migrations via
node scripts/migrate.mjs(requiresDATABASE_URL).
- dev target runs
Local development with Docker Compose
docker compose up --build
# App: http://localhost:5173
# Postgres: localhost:5432 (music/music)
.env.example has a sample DATABASE_URL. For dev compose, export DATABASE_URL in your shell or .env before docker compose up.
Production stack (Portainer)
docker-compose.prod.ymldefines a two-service stack:web(this app), exposes3000internally and maps to host8080by default.db(Postgres 16-alpine) with a persisted volumedb_data.
- The
webservice expectsDATABASE_URL=postgres://music:music@db:5432/music. - The image reference uses a tag placeholder:
registry.aqueous.network/music-lookup:${API_IMAGE_TAG:-latest}.- Portainer webhook will pass
API_IMAGE_TAGso the stack pulls the requested tag.
- Portainer webhook will pass
Configurable ports:
WEB_PORT– external host port (default8080).WEB_INTERNAL_PORT– internal container port (default3000).PORT– Express server port inside the container. This is set automatically fromWEB_INTERNAL_PORT.
Example overrides in Portainer stack env/variables:
WEB_PORT=9000
WEB_INTERNAL_PORT=4000
Then access the app at http://localhost:9000.
Portainer webhook deployment
- Script:
deploy-portainer.sh- Triggers a Portainer stack redeploy via webhook.
- Appends
API_IMAGE_TAG(and optionalUI_IMAGE_TAG) query params to the webhook. - For production, the script polls the stack status until success/failure.
- Required env when running the script (set by CI):
PORTAINER_URL(e.g.,https://port.aqueous.network)PORTAINER_API_KEY(Portainer API key)WEBHOOK_URL(stack webhook URL generated by Portainer)- Args:
<STACK_ID> <environment: staging|production> <image_tag>
Forgejo Actions workflow
Workflow: .forgejo/workflows/ci.yml
- test: npm ci + lint + typecheck + vitest
- build: Builds and pushes image to
registry.aqueous.network/music-lookupwith tags:${GITHUB_SHA},${BRANCH_NAME}, andlatest
- deploy-staging: on
developbranch push - deploy-production: on
mainbranch push
Configure repository secrets in Forgejo:
PORTAINER_API_KEY– Portainer API keyPORTAINER_WEBHOOK_URL– Webhook URL for your stack (Portainer > Stacks > Webhooks)
Environment variables inside the workflow (edit as needed):
REGISTRY– defaults toregistry.aqueous.networkIMAGE_NAME– defaults tomusic-lookupPORTAINER_STACK_ID_STAGING/PORTAINER_STACK_ID_PRODUCTION– Update to your stack IDs
First-time production setup (manual)
- Build and push an initial image locally or trigger the CI build on
main. - In Portainer, create a new Stack using
docker-compose.prod.ymlfrom this repo.- Add an environment variable
API_IMAGE_TAGwith the image tag you want to deploy (e.g.,latestor a commit SHA). - Ensure the stack has network egress to your registry.
- Add an environment variable
- Create a Webhook for the stack and copy its URL.
- Add the webhook to Forgejo repository secrets as
PORTAINER_WEBHOOK_URL. - Set
PORTAINER_STACK_ID_*values in the workflow to your stack ID. - Push to
developormainto trigger deploy.
Notes on production serving
- The production image now runs a Qwik City Node/Express server (SSR). You can add middleware (compression, security headers), logging, and health checks as needed.
Express Server
This app has a minimal Express server implementation. After running a full build, you can start the server using the command:
npm run serve
Then visit:
- When running directly: http://localhost:3000/
- When running via production Docker compose: http://localhost:8080/