Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Symurr API

Backend for Symurr — escalating payment reminders for freelancers, agencies, and consultants.

Node.js + Express + PostgreSQL (via Prisma). This is the API only — it does not include the landing page, which is a separate static HTML file.


1. What's actually working vs. what needs your keys

Piece Status
Auth (register/login/refresh/logout) Fully implemented
Invoice CRUD, scoped per user Fully implemented
Escalation logic (5-stage reminder ladder) Fully implemented
Daily cron job that sends reminders Fully implemented — needs SMTP + Twilio credentials to actually send
Stripe Connect onboarding link Implemented — needs your Stripe account + STRIPE_SECRET_KEY
Stripe webhook (marks invoices paid) Implemented — needs STRIPE_WEBHOOK_SECRET from the Stripe dashboard
Reading real invoices from Stripe automatically Not built yet — right now you create invoices via the API. Auto-importing from a connected Stripe account is the natural next step; ask me when you're ready.

Nothing here has been tested against a live Stripe/Twilio account — I don't have network access to those services from where I built this. You'll need to test those integrations yourself with test-mode keys first.


2. Local setup

# 1. Install dependencies
npm install

# 2. Copy the env template and fill in real values
cp .env.example .env

# 3. Create a Postgres database (locally, or a free one on Supabase/Neon/Railway)
#    then put its connection string in DATABASE_URL inside .env

# 4. Generate the Prisma client and run the first migration
npx prisma migrate dev --name init

# 5. Start the dev server (auto-restarts on file changes)
npm run dev

The API will be running at http://localhost:4000. Check GET /health to confirm it's up.


3. Getting your API keys

  • Postgres: easiest free option while learning is Neon or Supabase — both give you a connection string in ~2 minutes.
  • Stripe: sign up at stripe.com, grab your test mode secret key from the Dashboard → Developers → API keys. For the webhook secret, use the Stripe CLI (stripe listen --forward-to localhost:4000/api/stripe/webhook) while developing.
  • Twilio (SMS): sign up at twilio.com, get a trial number + Account SID + Auth Token.
  • SMTP (email): for testing, Mailtrap is the easiest — it catches emails in a sandbox inbox instead of really sending them.

Never commit .env — it's already in .gitignore.


4. How users would actually access this

There's no separate "how do I let people in" step needed beyond what's built: POST /api/auth/register creates an account, POST /api/auth/login signs in. A frontend (the landing page, eventually turned into a full app, or a separate React dashboard) would call these endpoints and store the returned access token in memory (not localStorage — see security notes below).


5. Security decisions made in this codebase, and why

  • Passwords: hashed with bcrypt (cost factor 12), never stored or logged in plain text.
  • Access tokens: short-lived JWTs (15 min default), sent in the response body, meant to be held in memory on the frontend — not localStorage, which is readable by any injected script (XSS).
  • Refresh tokens: long-lived (7 days), but stored as httpOnly, sameSite=strict cookies — invisible to JavaScript, which blocks both XSS token theft and CSRF. The token itself is never stored in the database in plain form — only its SHA-256 hash — so a leaked database dump can't be replayed as a live session. Refresh tokens rotate on every use: reusing an old one is treated as invalid.
  • Login errors are generic ("Invalid email or password") on purpose — a different error for "no such account" vs "wrong password" lets attackers enumerate which emails have accounts.
  • Rate limiting: a strict limiter (10 requests / 15 min) on auth endpoints specifically, to slow down brute-force and credential-stuffing attempts.
  • Every invoice query is scoped to userId: there is no endpoint where one user can read or modify another user's data by guessing an ID.
  • Input validation: every request body is validated with Zod before it touches a database query — malformed or unexpected fields are rejected early.
  • Stripe webhook signature verification: the webhook route is deliberately wired to receive the raw request body (before Express's JSON parser touches it), because Stripe's signature check requires the exact original bytes. A request that fails this check is rejected outright — this is what stops someone from forging a fake "invoice paid" event.
  • helmet: sets secure HTTP headers (CSP, no-sniff, no clickjacking, etc.) by default.
  • Errors never leak internals: the global error handler logs full details server-side but only ever sends a generic message to the client for unexpected (500-level) errors.

What this does not yet include, and you should add before going to production with real users' money: email verification on signup, 2FA, an account-lockout policy after repeated failed logins, audit logging, and a proper secrets manager instead of a .env file.


6. Project structure

src/
  app.js                 — Express app setup, middleware, route mounting
  server.js               — entry point, starts the server + cron scheduler
  config/                  — Prisma client, Stripe client
  middleware/              — auth check, rate limiting, error handling
  routes/                  — one file per resource (auth, invoices, stripe)
  controllers/              — request handlers, one per resource
  services/                 — reminder stage copy, email sending, SMS sending
  jobs/                     — the daily cron job that actually sends reminders
  utils/                    — Zod validators, token helpers
prisma/schema.prisma        — database schema (User, Invoice, Client, etc.)

About

a practical dept Nudge

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages