Backend for StemBridge, a music collaboration app for producers working across projects, versions, files, comments, invites, and activity history.
The API is built with Express, TypeScript, Prisma, PostgreSQL, JWT auth, and S3-backed file storage.
- Node.js
- Express
- TypeScript
- Prisma
- PostgreSQL
- Vitest + Supertest
- AWS S3
src/
app.ts
server.ts
config/
lib/
middleware/
modules/
utils/
prisma/
schema.prisma
seed.ts
tests/
- Install dependencies
npm install- Copy the env file
cp .env.example .env- Generate the Prisma client
npm run prisma:generateNODE_ENV=development
PORT=4000
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/stembridge?schema=public"
JWT_SECRET="replace-with-a-long-random-string"
JWT_EXPIRES_IN=7d
CORS_ORIGINS=http://localhost:3000
JSON_BODY_LIMIT=1mb
URL_ENCODED_BODY_LIMIT=100kb
AUTH_RATE_LIMIT_WINDOW_MS=900000
AUTH_RATE_LIMIT_MAX=10
UPLOAD_FILE_SIZE_LIMIT_BYTES=104857600
S3_REGION=us-east-1
S3_BUCKET=stembridge-dev
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
APP_BASE_URL=http://localhost:4000Notes:
CORS_ORIGINSis a comma-separated allowlist.DATABASE_URLis used by Prisma and the runtime client.UPLOAD_FILE_SIZE_LIMIT_BYTEScontrols multer memory upload limits.APP_BASE_URLis used when building app-facing URLs.
Run migrations:
npm run prisma:migrateSeed demo data:
npm run prisma:seedOpen Prisma Studio:
npm run prisma:studioStart the dev server:
npm run devBuild the project:
npm run buildStart the built server:
npm run startRun tests:
npm run testSuccess responses:
{
"message": "Project created successfully",
"data": {}
}Error responses:
{
"message": "Validation failed",
"details": {}
}Protected routes require:
Authorization: Bearer <jwt>GET /health
POST /auth/registerPOST /auth/loginGET /auth/me
POST /projectsGET /projectsGET /projects/:projectId
POST /projects/:projectId/invitesGET /projects/:projectId/invitesPOST /invites/:token/accept
POST /projects/:projectId/versionsGET /projects/:projectId/versionsGET /versions/:versionIdGET /versions/:versionId/download
POST /versions/:versionId/files/uploadPOST /versions/:versionId/files/metadataGET /versions/:versionId/files
POST /versions/:versionId/commentsGET /versions/:versionId/commentsDELETE /comments/:commentId
GET /projects/:projectId/activity?page=1&pageSize=20
Request:
POST /auth/register
Content-Type: application/json{
"email": "producer@stembridge.dev",
"password": "Password123!"
}Response:
{
"message": "Registration successful",
"data": {
"token": "<jwt>",
"user": {
"id": "user-id",
"email": "producer@stembridge.dev",
"createdAt": "2026-04-22T10:00:00.000Z",
"updatedAt": "2026-04-22T10:00:00.000Z"
}
}
}Request:
POST /auth/login
Content-Type: application/json{
"email": "producer@stembridge.dev",
"password": "Password123!"
}Response:
{
"message": "Login successful",
"data": {
"token": "<jwt>",
"user": {
"id": "user-id",
"email": "producer@stembridge.dev",
"createdAt": "2026-04-22T10:00:00.000Z",
"updatedAt": "2026-04-22T10:00:00.000Z"
}
}
}Request:
POST /projects
Authorization: Bearer <jwt>
Content-Type: application/json{
"name": "Night Session",
"bpm": 126,
"musicalKey": "F Minor"
}Response:
{
"message": "Project created successfully",
"data": {
"project": {
"id": "project-id",
"name": "Night Session",
"bpm": 126,
"musicalKey": "F Minor",
"owner": {
"id": "user-id",
"email": "producer@stembridge.dev"
},
"collaboratorCount": 1,
"versionCount": 0,
"collaborators": [
{
"id": "membership-id",
"joinedAt": "2026-04-22T10:01:00.000Z",
"user": {
"id": "user-id",
"email": "producer@stembridge.dev"
}
}
],
"latestVersion": null
}
}
}Request:
POST /projects/:projectId/versions
Authorization: Bearer <jwt>
Content-Type: application/json{
"notes": "Second mix pass with tighter drums"
}Response:
{
"message": "Version created successfully",
"data": {
"version": {
"id": "version-id",
"projectId": "project-id",
"versionNumber": 2,
"notes": "Second mix pass with tighter drums",
"createdBy": {
"id": "user-id",
"email": "producer@stembridge.dev"
},
"fileAssets": [],
"comments": []
}
}
}Request:
POST /versions/:versionId/comments
Authorization: Bearer <jwt>
Content-Type: application/json{
"timestampSeconds": 42.5,
"text": "The bass transition works well here."
}Response:
{
"message": "Comment created successfully",
"data": {
"comment": {
"id": "comment-id",
"versionId": "version-id",
"timestampSeconds": 42.5,
"text": "The bass transition works well here.",
"author": {
"id": "user-id",
"email": "producer@stembridge.dev"
}
}
}
}Request:
POST /versions/:versionId/files/upload
Authorization: Bearer <jwt>
Content-Type: multipart/form-dataForm fields:
file: binary filetype: one ofSTEM,MIX,MIDI,SAMPLE,OTHER
Response:
{
"message": "File uploaded successfully",
"data": {
"file": {
"id": "file-id",
"versionId": "version-id",
"name": "rough-mix.wav",
"originalName": "Rough Mix.wav",
"type": "MIX",
"mimeType": "audio/wav",
"sizeBytes": 13104442,
"storageKey": "projects/project-id/versions/version-id/1700000000000-Rough-Mix.wav",
"url": "https://bucket.s3.region.amazonaws.com/...",
"createdAt": "2026-04-22T10:05:00.000Z"
}
}
}