2023Solo developer — API and frontend
Arithmetic Calculator — API + Vue SPA
A JWT-authenticated calculator API with per-operation credits and a Vue 3 SPA on top.
- Node.js
- Express
- PostgreSQL
- Sequelize
- JWT
- Zod
- Vue 3
- Vite
- Pinia
- PrimeVue
Problem
Most calculator demos are stateless and never answer the harder question: how do you make a compute service multi-user, metered and auditable? I wanted an exercise that forced the real machinery — auth, per-user accounting and a paper trail — not just arithmetic.
The result is an API where each operation has a credit cost charged against the user's balance, and every computation is stored as a record with full pagination, search and audit history.
Approach
Credit-based API, not free compute
Express + Sequelize + PostgreSQL. A catalog of operations (addition, subtraction, multiplication, division, square root, random string) each declares a credit cost. Creating a record runs the operation, charges the cost and stores the resulting balance. Records support pagination, sorting, text search and soft delete — because destroying an audit trail is usually the wrong default.
I rejected stateless compute endpoints: they make usage unaccountable by design, which is exactly the problem the exercise was about.
OpenAPI contract first
The full contract lives in `api_services_v1.yml` (OpenAPI 3.0). Zod validates every request at the boundary, `bcryptjs` hashes passwords, and JWTs authorize the protected routes. Jest + ts-jest cover the API.
I rejected ad-hoc validation scattered through handlers: with a written contract, the Vue UI can be built against the YAML instead of against implementation details.
Vue 3 SPA against the same contract
Vue 3 (Options API) + Vite + Pinia + PrimeVue + Axios. Sign-in stores the JWT locally; a dashboard shows available operations and the balance; a DataTable renders records with pagination, search and soft delete; a 401 response logs the user out and clears the token. Vitest covers the UI.
I rejected putting auth decisions in components: a single Axios interceptor owns the 401 handling, so every screen behaves the same way without knowing about sessions.
Results
- Both halves are public and CI-tested (GitHub Actions): the API (OpenAPI 3.0 contract, Jest) and the Vue SPA (Vitest + @vue/test-utils).
- Complete OSS reference for metered, multi-user REST: auth, catalog pricing, immutable-ish audit records and soft delete in one small codebase.