Skip to content

jussipalanen/jussispace-backend

Repository files navigation

JussiSpace Backend

Node.js REST API for apartment and office space rentals. Built with Express 5, Prisma ORM, and MySQL. Runs in Docker.

Tech stack

Layer Technology
Runtime Node.js 20
Framework Express 5
ORM Prisma 6
Database MySQL 8.0
Auth JWT (jsonwebtoken)
Image processing sharp (WebP conversion)
File upload multer
Mail Nodemailer + Brevo SMTP
i18n i18next (en, fi)
Container Docker + Docker Compose
Linting ESLint 9 (flat config)
CI/CD GitHub Actions

Project structure

jussispace-backend/
├── src/
│   ├── server.js                   # Entry point — starts HTTP server
│   ├── app.js                      # Express app (middleware, routes, static files)
│   ├── i18n.js                     # i18next setup
│   ├── routes/
│   │   ├── index.js                # Mounts all routes under /api
│   │   ├── auth.js
│   │   ├── users.js
│   │   ├── properties.js           # Includes image sub-routes
│   │   ├── amenities.js
│   │   └── orders.js
│   ├── controllers/
│   │   ├── auth.js                 # Register, login, me, logout, password reset
│   │   ├── users.js                # User CRUD
│   │   ├── properties.js           # Property CRUD
│   │   ├── propertyImages.js       # Image upload, update, delete
│   │   ├── amenities.js            # Amenity CRUD
│   │   └── orders.js               # Order CRUD + approval workflow
│   ├── middleware/
│   │   ├── authenticate.js         # JWT auth middleware
│   │   ├── authorize.js            # Role-based access control
│   │   ├── upload.js               # multer config (memory storage, 10 MB limit)
│   │   └── errorHandler.js         # Centralised error handler
│   ├── mail/
│   │   ├── mailer.js               # Nodemailer transport (Brevo SMTP)
│   │   └── templates/
│   │       ├── welcome.js
│   │       ├── resetPassword.js
│   │       └── passwordChanged.js
│   └── locales/
│       ├── en/translation.json
│       └── fi/translation.json
├── prisma/
│   ├── schema.prisma               # User, Property, PropertyImage, Amenity, Order
│   └── seed.js                     # Seeds users, amenities, properties, orders
├── scripts/
│   ├── deploy.js                   # Tag and push a production release
│   ├── generate-postman.js         # Generates Postman collection
│   ├── sync-translations.js        # Syncs missing translation keys
│   └── user.js                     # CLI user management tool
├── uploads/
│   └── properties/                 # Uploaded images (WebP) — gitignored
├── postman/
│   └── jussispace.postman_collection.json
├── .github/
│   └── workflows/
│       ├── ci.yml                  # CI: lint, security audit, Docker build
│       └── deploy.yml              # CD: build, push to Docker Hub, deploy to EC2
├── Dockerfile                      # 3-stage multi-stage build
├── docker-compose.yml              # Local dev — app + mysql
├── docker-compose.prod.yml         # Production — uses Docker Hub image
├── dev                             # Developer command shortcuts
├── eslint.config.mjs
├── .env.example                    # Local environment variable template
├── .env.production.example         # Production environment variable template
├── CHANGELOG.md
└── .dockerignore

Getting started

Prerequisites

  • Node.js 20+ (use nvm use to switch — version pinned in .nvmrc)
  • Docker + Docker Compose

Docker (recommended)

# Build and start all services (MySQL + app)
./dev restart

# View logs
./dev logs

Local development (without Docker)

npm install
cp .env.example .env
# Edit .env — set DATABASE_URL to point to your local MySQL instance,
# and set JWT_SECRET and mail variables

Set up the database:

./dev db:generate   # generate Prisma client
./dev db:push       # create tables from schema
./dev db:seed       # populate with test data

Start the server:

./dev dev           # or: npm run dev

The API is available at http://localhost:3000.


Environments

Environment URL
Local dev http://localhost:3000/api
Production https://backend-lab-jussispace.jussialanen.com/api

Environment variables

Copy .env.example to .env for local dev, or .env.production.example to .env on the EC2 server.

Variable Default Description
NODE_ENV development Runtime environment
PORT 3000 HTTP port
DATABASE_URL Prisma MySQL connection string
JWT_SECRET Secret key for signing JWT tokens
JWT_EXPIRES_IN 7d JWT token expiry duration
ALLOWED_ORIGINS * Comma-separated CORS origins
FRONTEND_URL Frontend base URL (used in password reset emails)
MAIL_ENABLED false Set to true to enable email sending
MAIL_HOST SMTP host
MAIL_PORT 587 SMTP port
MAIL_USERNAME SMTP username
MAIL_PASSWORD SMTP password
MAIL_FROM_ADDRESS Sender email address
MAIL_FROM_NAME Sender display name
MYSQL_ROOT_PASSWORD MySQL root password (production)
MYSQL_DATABASE MySQL database name (production)
MYSQL_USER MySQL user (production)
MYSQL_PASSWORD MySQL user password (production)

Important: Always set a strong JWT_SECRET before deploying to production.


API

See API.md for full endpoint documentation.

Endpoints overview

Method Path Auth Description
GET /api/ API title and description
GET /api/health Health check
GET /api/translations i18n strings (?lang=en|fi)
POST /api/auth/register Register
POST /api/auth/login Login (returns JWT)
GET /api/auth/me Bearer Current user
POST /api/auth/logout Bearer Logout
POST /api/auth/lost-password Request password reset
POST /api/auth/reset-password Complete password reset
GET /api/users admin List users
GET/PUT/DELETE /api/users/:id self or admin Manage user
GET /api/properties List properties (?type, ?city, ?status)
GET /api/properties/:id Get property
POST /api/properties admin/editor Create property
PUT/DELETE /api/properties/:id admin/editor Update / delete
GET /api/properties/:id/images List images
POST /api/properties/:id/images admin/editor Upload image (→ WebP)
PUT/DELETE /api/properties/:id/images/:imageId admin/editor Update / delete image
GET /api/amenities List amenities
GET /api/amenities/:id Get amenity
POST /api/amenities admin/editor Create amenity
PUT/DELETE /api/amenities/:id admin/editor Update / delete
GET /api/orders admin List orders (?status, ?userId, ?propertyId)
GET /api/orders/:id self or admin Get order
POST /api/orders Bearer Create order
PUT /api/orders/:id admin Approve / decline order
DELETE /api/orders/:id self or admin Delete order

Database

Models

Model Table Description
User users Application users with role-based access
Property properties Apartment and office listings
PropertyImage property_images Multiple images per property (WebP)
Amenity amenities Reusable feature tags
PropertyAmenity property_amenities Property ↔ amenity join table
Order orders Rental bookings with approval workflow

Prisma commands

./dev db:generate   # generate Prisma client after schema changes
./dev db:migrate    # create and apply a new migration
./dev db:push       # push schema directly (dev only, no migration file)
./dev db:seed       # seed test data
./dev db:studio     # open Prisma Studio GUI

Seed data

Run ./dev db:seed to populate the database with test data:

What Count Details
Users 2 superadmin@example.com (admin), tenant@example.com (subscriber)
Amenities 12 WiFi, Parking, Sauna, Elevator, AC, etc.
Properties 9 5 apartments + 4 offices in Helsinki and Espoo
Orders 4 Covers all statuses: approved, pending, declined

All seed passwords: password123


Image uploads

Images are uploaded via POST /api/properties/:id/images as multipart/form-data with the field name image.

  • Accepted formats: JPEG, PNG, WebP, GIF (max 10 MB)
  • Automatically converted to WebP at 82% quality via sharp
  • Stored at /uploads/properties/{propertyId}/{uuid}.webp
  • Publicly accessible at http://localhost:3000/uploads/properties/{propertyId}/{uuid}.webp
  • Setting isPrimary: true unsets the primary flag on all other images for that property
  • Deleting an image removes the file from disk

In production (EC2), the uploads directory is a named Docker volume — files persist across deployments.


Docker

Dockerfile — 3-stage build

Stage Purpose
deps Installs production dependencies only (npm ci --omit=dev)
build Installs all deps and runs prisma generate
production Copies prod node_modules + Prisma client, creates uploads dir, runs as non-root user

Local compose services

Service Image Port
mysql mysql:8.0 3306
app Built from Dockerfile 3000

Production compose (docker-compose.prod.yml)

Used on EC2. Pulls the pre-built image from Docker Hub instead of building locally. Named volumes keep MySQL data and uploaded images across deployments.

# First boot on EC2
IMAGE_TAG=latest DOCKER_USERNAME=yourname docker compose -f docker-compose.prod.yml up -d

Deploy to production

Deployments are triggered by pushing a git tag in the format x-x-x-production.

# Using the dev script (prompts for version if omitted)
./dev deploy 1-0-0
./dev deploy        # interactive prompt

This creates and pushes the tag 1-0-0-production, which triggers the GitHub Actions pipeline:

  1. Builds the Docker image
  2. Pushes yourname/jussispace-backend:1-0-0-production + :latest to Docker Hub
  3. SSHs into EC2, pulls the new image, restarts the app container
  4. Runs prisma db push for schema migrations
  5. Prunes old images to free disk space

You can also trigger a deploy manually from GitHub → Actions → Build and Deploy → Run workflow.

Required GitHub secrets

Secret Description
DOCKER_USERNAME Docker Hub username
DOCKER_PASSWORD Docker Hub access token
EC2_HOST EC2 public IP address
EC2_SSH_KEY Contents of your .pem file

dev — developer shortcuts

source ./dev      # load aliases into your shell session
./dev <command>   # run a single command directly
Command Description
dev Sync schema, seed DB, start nodemon
start node src/server.js (production mode)
lint Run ESLint
lint:fix Run ESLint with auto-fix
install npm install
postman Regenerate Postman collection
i18n:sync Add missing translation keys
user CLI user management (list/create/update/delete)
deploy Tag and push a production release
db:generate prisma generate
db:migrate prisma migrate dev
db:push prisma db push
db:seed Seed the database
db:studio Open Prisma Studio
up docker compose up -d
down docker compose down
restart Rebuild and restart containers
build docker compose build
logs docker compose logs -f
ps docker compose ps
shell Shell inside the app container
dbshell MySQL CLI inside the mysql container
clean Remove containers and volumes (destructive)

CI/CD — GitHub Actions

CI (ci.yml)

Triggered on push and pull requests to main / master.

Job What it checks
Lint ESLint across src/
Security Audit High-severity vulnerabilities in production deps
Docker Build Image builds successfully

CD (deploy.yml)

Triggered by a x-x-x-production git tag or manual dispatch.

Job What it does
Build & Push Builds Docker image, pushes to Docker Hub
Deploy SSHs into EC2, pulls image, restarts app, runs migrations

Security

  • Helmet — secure HTTP response headers
  • CORS — configurable allowed origins via ALLOWED_ORIGINS
  • Rate limiting — 100 requests per 15 minutes per IP
  • Body size limit — JSON payloads capped at 10 KB
  • JWT authentication — stateless, configurable expiry
  • Role-based access controladmin, editor, subscriber
  • Password hashing — bcrypt with 12 salt rounds; never returned by the API
  • Password reset tokens — cryptographically random, expire after 1 hour, single-use
  • Non-root Docker user — container runs as appuser
  • npm audit in CI — high-severity vulnerabilities block the pipeline

About

The Node.js backend server for the Jussispace frontend application to read apartments and office spaces by API routes.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors