studzee-api: Express and TypeScript backend for the Studzee education platform.
765
studzee-api is the backend for Studzee, an educational content platform. It
owns content and caching, Clerk authentication, Expo push notifications,
transactional email, the Clerk webhook, and the audit logs.
Express 4 on Node 22, written in TypeScript. Built from MasterBhuvnesh/studzee.
docker run -d --name studzee-api \
-p 4000:3000 \
-e NODE_ENV=production \
-e PORT=3000 \
-e MONGO_URI="mongodb://..." \
-e DATABASE_URL="postgresql://..." \
-e REDIS_URL="redis://..." \
-e CLERK_SECRET_KEY="sk_..." \
-e CLERK_PUBLISHABLE_KEY="pk_..." \
-e S3_REGION="..." \
-e S3_ACCESS_KEY_ID="..." \
-e S3_SECRET_ACCESS_KEY="..." \
-e S3_BUCKET_IMAGES="images" \
-e S3_BUCKET_PDFS="pdfs" \
-e S3_ENDPOINT="https://<ref>.storage.supabase.co/storage/v1/s3" \
-e S3_PUBLIC_URL="https://<ref>.supabase.co/storage/v1/object/public" \
-e SMTP_HOST="..." \
-e SMTP_USER="..." \
-e SMTP_PASSWORD="..." \
-e EMAIL_FROM="Studzee <[email protected]>" \
<namespace>/studzee-api:latest
The image serves on 3000, not 4000. It declares EXPOSE 3000 and its
healthcheck probes 3000, so PORT must be 3000 inside the container. Publish
it wherever you like on the host, for example -p 4000:3000.
Setting PORT to anything else produces a container that serves traffic
correctly and reports unhealthy forever, because the healthcheck is still
probing 3000.
| Tag | What it is |
|---|---|
latest | The most recent release. Moved only by a backend-v* git tag. |
4.0.0 | A specific release. Immutable. |
<7 char sha> | A manual build from a branch. Never tagged latest, because an unreleased branch should not become what a deploy target pulls. |
Pin to a version tag in production. latest moves under you.
Sixteen variables are required and the process exits at startup without any one of them. The Zod schema parses at import time and throws on the first problem, so a missing variable is a named boot failure rather than a runtime surprise.
CLERK_SECRET_KEY, CLERK_PUBLISHABLE_KEY, MONGO_URI, DATABASE_URL,
REDIS_URL, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY,
S3_BUCKET_IMAGES, S3_BUCKET_PDFS, S3_ENDPOINT, S3_PUBLIC_URL,
SMTP_HOST, SMTP_USER, SMTP_PASSWORD, EMAIL_FROM.
Also set these, which have defaults that are wrong for a deployment:
NODE_ENV=production. Required. The default is development.PORT=3000. See above.CLERK_WEBHOOK_SIGNING_SECRET if Clerk webhooks are enabled. Without it
POST /webhooks/clerk answers 500, because the svix signature is that
endpoint's only authentication and an unset secret is treated as a hard
failure rather than a skipped check.Full reference, including the optional variables and their defaults, is in
.docs/DEPLOYMENT.md.
Two notes on the S3 variables. S3_ENDPOINT is the S3 API host and
S3_PUBLIC_URL is the public object host. On Supabase these are different
hosts and neither can be derived from the other, which is why both exist.
| Endpoint | Purpose |
|---|---|
/health/liveness | Process is up. This is what the image healthcheck probes. |
/health/readiness | Round trips MongoDB, Postgres and Redis. |
curl http://localhost:4000/health/readiness
# {"status":"ready","checks":{"db":"ok","postgres":"ok","redis":"ok"}}
Readiness issues a real query against each store rather than reading driver
connection flags, so it cannot report healthy while a dependency is down. Any
"error" names the store that is not answering.
prisma migrate deploy before
the application. DATABASE_URL therefore has to be reachable at boot or the
container exits 1 with P1001 before any application code runs. It also
means every replica attempts the migration, so run migrations separately if
you scale beyond one instance.MONGO_URI does not fail the
connection, it fails the first query, so it surfaces as a 500 on a route
rather than an error at boot.CLERK_PUBLISHABLE_KEY throws at parse time and reaches the error handler as
a 500, which makes an unauthenticated request look like a server fault
instead of a 401.appuser).publicMetadata.role === 'admin', and no code path sets it. Until at least
one user has it, the entire /admin surface answers 403 to everyone.Routes are grouped under /content, /pdfs, /notifications, /admin,
/webhooks and /health. Every endpoint is documented in
API.md,
written against what the handlers actually return.
See the repository.
Content type
Image
Digest
sha256:d2a625856…
Size
143.6 MB
Last updated
24 days ago
docker pull bhuvneshverma/studzee-api