Introduction
Running the Demo with Docker
Demo Login Credentials
Video Demo
Technical Challenges
Running the Backend Locally Without Docker
Running the Frontend Locally Without Docker
Authentication
This is a comprehensive code sample for a task creation and management app utilizing Node.js and Express.js for the backend and Next.js with Mantine UI and Tailwind CSS for the frontend. Both backend and frontend use TypeScript.
The backend is functional and uses PostgreSQL with Prisma ORM and Supabase.
Swagger documentation is automatically generated from the API description provided in openapi.yaml
and is available at /api-docs
.
The frontend UI allows users to display a list of tasks, create new tasks, and edit or delete existing tasks.
The backend includes a Task controller with CRUD operations: create, read (get all non-deleted tasks or get a task by ID), update, and soft-delete and a sample login route.
There are two sample CI workflows (backend_ci.yml
and frontend_ci.yml
) available in the .github/workflows
directory. Each workflow is triggered automatically on a git push
or pull request creation when either backend or frontend files are modified. They run ESLint and Prettier checks and unit tests. The backend includes sample unit tests for the Task controller.
You can spin up the whole project with Docker.
To run the demo locally, please ensure that Docker and Docker Compose are installed on your machine. On macOS, you can install Docker Desktop which includes Docker Compose.
Check out the repository and run the following commands to spin up the whole project:
cd react-api-data-sample
cp .env.example .env
docker compose up --build
The frontend will be available at http://localhost:3000, the backend at http://localhost:4000, and the Swagger UI with the API docs at http://localhost:4000/api-docs/. The test PostgreSQL database will be created and seeded with sample data.
(If there are conflicts with previously run containers, docker compose down -v
should fix it.)
testuser@example.com
ReactDemo123
These credentials should be used for testing purposes only.
React/Next.js UI:
https://youtu.be/yViq7Q-s5TQ
-
So, the app is simple but I tried to keep the overall project setup (with useful NPM scripts, linter checks and architecture (UI libraries, auth, contexts) close to a real-world project.
-
The backend part is rather straightforward. It has auth routes for authentication and auth validation with Supabase (the auth token is sent to client as a cookie), request validation with the express-validator library, and uses popular middleware. The DB schema is defined in
backend/prisma/schema.prisma
. There is a script to seed the DB with test data.The API docs UI is created with the help of Swagger parsing a YAML doc with the API description in OpenAPI format and runs locally at http://localhost:4000/api-docs/
-
The frontend part is more interesting. It is built with Next.js, which is a React-based framework supporting server-side rendering. So, while it might be an overkill in case of a typical single-page application, like a dashboard, Next.js is a very useful tool when it comes to rendering SEO-optimized content fast (for example, on marketing websites).
Also, Next.js can provide some benefits for rendering pages of a single-page app too, but it requires some adjustments from the simple client-side fetching with “Loading…” text my sample app uses right now. With Next.js, it is possible to render a “skeleton” of the app’s page very fast and then just stream data to the frontend from backend. And in case of regular client-side fetching in a real-world app I would prefer to use TanStack Query (former React Query), which was used in the previous project I worked on.
The most challenging part of the current sample was making frontend libraries play well with Next.js and with each other because of their somewhat non-standard setup.
Fortunately, Tailwind CSS, which I used for simple UI styling (like UI containers, spacing, etc.), works with Next.js out of the box.
But I was recommended Mantine UI, which is used successfully in a couple of startups I know as a lightweight, highly customizable alternative to Material UI (which we used in ar my previous workplace). I learned that their developers are happy, the customers are happy, and decided to try Mantine in my code sample even though Material UI is a more mature solution.
Since I didn’t work with Mantine before and only worked with Tailwind CSS a bit, I had to spend some time learning Mantine and figuring out how to make Mantine work with Next.js (because of server-side rendering) and how to make Mantine and Tailwind CSS work together and toggle light and dark themes with a single switch. So, I think now I got the hang of it.
Besides, adding the popular React Hook Form library for managing Mantine form input elements (I had to use both controlled (fully manageable) and uncontrolled inputs because of how Mantine does it) and the Zod library for Mantine input validation also took some time and workarounds.
Auth status and theme are provided across the app with Auth and Theme React contexts. Next.js middleware (which runs on the server) is used to validate auth before navigating to a route.
-
There are also two simple GitHub workflows running linter checks and basic tests on git push or pull request, if a file in a corresponding folder changed.
To run the backend locally, follow these steps:
-
Install Dependencies:
- Ensure you have Node.js v22.12.0 and npm installed.
- Navigate to the backend's root directory (where
package.json
is located). - Run:
npm install
-
Configure PostgreSQL:
- Install PostgreSQL if not already installed.
- Create a database named
task_db
. - Note the database name, user, and password for the
.env
file.
-
Create an
.env
File:- In the backend root directory (
backend
), create a file named.env
with the following content:
# App PORT=4000 FRONTEND_URL=http://localhost:3000 # Supabase auth SUPABASE_URL=https://lmomvxdophesuqdghzpv.supabase.co SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imxtb212eGRvcGhlc3VxZGdoenB2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzU5NTYwODUsImV4cCI6MjA1MTUzMjA4NX0.SgW7W43lOFF40TiG0Q-i5wSWM_b-6B6SAt7DXI5NVpI # PostgreSQL user (replace user and password with your PostgreSQL credentials) DATABASE_URL=postgresql://user:password@localhost:5432/task_db # Swagger UI ENABLE_SWAGGER=true
Security Notice: The above Supabase credentials are for demonstration purposes only and should not be used for production. Ensure proper security configurations and avoid sharing real credentials publicly.
- In the backend root directory (
-
Run Prisma Setup (Database & Client Generation):
- Initialize the database and apply migrations (this creates the
Task
table automatically):
cd backend npm run prisma:migrate
- Generate the Prisma Client for interacting with the database:
npm run prisma:generate
- Seed the
Task
table with sample data using providedseed.ts
script:
npm run prisma:seed
If anything doesn't work as expected, check your PostgreSQL database user credentials in the
.env
file. - Initialize the database and apply migrations (this creates the
-
Start the Backend:
- Run the following commands:
# Development npm run dev # Production npm run build npm run start
-
Access the Application:
- API Base URL: http://localhost:4000
- Swagger API Documentation: http://localhost:4000/api-docs
-
Authenticating in Swagger:
- Log in via the Login route in the Swagger UI with the demo login credentials. The cookie will be set and you will be able to access the other routes.
-
Demo Login Credentials (for Testing Only):
Email: testuser@example.com Password: ReactDemo123
These credentials should be used for testing purposes only.
-
To log out and clear the cookie, use the Logout route in the Swagger UI.
-
If you want to run the frontend locally, create the following
.env.local
and.env.production
files in the frontend root directory:.env.local:
NEXT_PUBLIC_API_URL=http://localhost:4000
.env.production:
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
or just use
http://localhost:4000
for production too.
To run the frontend locally, first make sure that the backend is running at http://localhost:4000
and run the following commands from the root directory:
cd frontend
npm run build
npm run start
The frontend will be available at http://localhost:3000.
- The backend uses HTTP cookies for authentication.
- When a user logs in with Supabase auth, the backend sets an
authToken
cookie containing the authentication token. - All subsequent requests to protected endpoints require the
authToken
cookie.
To authenticate, send a POST
request to /api/auth/login
with the following JSON payload:
{
"email": "testuser@example.com",
"password": "ReactDemo123"
}
-
If authentication is successful, the response will include a
Set-Cookie
header with authToken. -
The client must store and send this cookie in all subsequent requests.
- Navigate to http://localhost:4000/api-docs.
- Go to the POST
/api/auth/login
endpoint. - Enter the demo credentials and execute the request.
- Once logged in, Swagger will automatically store the authentication cookie.
- Now you can access protected routes like GET
/api/tasks
without manually setting an authorization token. - To log out, send a POST request to /api/auth/logout. This will clear the authentication cookie.
curl -X POST http://localhost:4000/api/auth/logout