This repository provides a minimal full-stack example implemented with React (Vite) on the frontend and Node.js on the backend with GraphQL.
Key features
- React + Vite + Apollo Client frontend
- Node.js + Express + Apollo Server (v4) backend
- Prisma ORM with PostgreSQL
- Authorization using CASL
- Docker Compose for local development (database + services)
- Node.js >= 24 for local development (Docker uses Node 24 in containers)
- npm (or yarn)
- Docker & Docker Compose (recommended for a reproducible environment)
This will build and start the services (Postgres, backend and frontend), apply migrations and seed the database automatically.
# From the repo root
docker-compose up --buildVisit the app:
- Frontend: http://localhost:5173
- GraphQL endpoint: http://localhost:4000/graphql
- GraphiQL UI: http://localhost:4000/graphiql
To stream backend logs when running containers detached:
docker-compose logs -f backendTo stop and remove containers:
docker-compose downTo kill a specific container and keep others running:
docker-compose kill <container name>- Ensure Node >= 24 on your machine. You can make use of the included
.nvmrcfile:
nvm use- Install backend dependencies and create a
.envfile:
cd backend
npm install
cp .env.example .env
# If you run the backend locally, change DATABASE_URL to use localhost instead of `db`.- Generate Prisma client, apply migrations and seed (development flow):
npm run prisma:generate
npx prisma migrate dev --name init # creates & applies a local migration
npm run seed # seeds some example data- Start the dev server:
npm run devcd frontend
npm install
npm run devThe Vite dev server runs at http://localhost:5173 by default.
- Migrations live in
backend/prisma/migrations/. Keep this directory in source control and rely onnpx prisma migrate dev(local). - The startup script in Docker runs migrations with
npx prisma migrate deploy || npx prisma db push, followed by the seeding scriptnode prisma/seed.js.
Create a new migration after editing the schema:
cd backend
npx prisma migrate dev --name add_field-
The backend uses JWT for authentication and CASL for authorization.
-
Dev seeding creates a set of sample users with
passwordas the password: -
bob@example.com / password (VIEWER)
-
manager@example.com / password (MANAGER)
-
admin@example.com / password (ADMIN)
- I didn't end up creating any field level authz rules in the API
- Given a bit more time and better knowledge of CASL I would redact certain data from Users in the API response. That data is redacted in the UI but a savvy user could inspect the network call and find that data.
- I got a little distracted and spent some time setting up GraphiQL and ESLint
- This turned out to be useful but it wasn't necessary.
- Several people asked me during interviews if I've ever used generative AI, so I decided to try using AI to scaffold the application for me but that ended up taking longer to get right than it probably would have taken me to just do it by hand.
- CoPilot seemed okay at setting up Docker once I had basically changed the entire intial monorepo structure.
- I tried to have CoPilot write a few basic unit tests but it kept writing tests and then trying to change the actual logic to make those tests pass instead of writing tests that covered the existing logic.
- Given more time I would like to:
- create an Audit table to capture the before and after as well as UserId and event time on all Create/Update/Delete events.
- Make it pretty. I added enough styling to make it usable but it's ugly. I started off just using
styles.cssand about halfway through decided to bring instyled-componentsto scope styles to elements and speed up development. When working on a small project by myself or a small team,styles.cssis managable but doesn't scale well as the project and team grows. - add more unit tests
- I normally would not seed the database during the process of spinning up docker but for this I was editing and deleting exhibits enough that I thought it was useful. Just understand that the more you run this the more of basically the same exhibits you will have because I don't force them to be unique. Alternatively after the database has been seeded, you can comment out line 19 in
backend/docker-entrypoint.sh.