14 KiB
Excellent. Building this with NestJS is a superb choice, especially if you or your team have a JavaScript/TypeScript background. NestJS provides a structured, scalable architecture that prevents many common pitfalls.
This guide will walk you through starting your project from scratch using NestJS, React, and PostgreSQL. We'll use Docker to run our database easily, which is a modern best practice.
Phase 0: Prerequisites (Your Developer Machine Setup)
- Node.js and npm: (Node LTS version). This is required for both the frontend and backend. Download Node.js
- Docker Desktop: We will use this to run our PostgreSQL database in a container. This is far easier than installing PostgreSQL directly on your system. Download Docker Desktop
- NestJS CLI: The command-line interface for creating and managing NestJS projects.
npm i -g @nestjs/cli - Git: For version control. Download Git
- A Code Editor: VS Code is highly recommended due to its excellent TypeScript support.
- An API Client: Postman or Insomnia to test your API endpoints.
Phase 1: Project Initialization & Structure
Just like before, we'll create a clean project structure.
# 1. Create a main project folder
mkdir incident-reporter-app
cd incident-reporter-app
# 2. Initialize a Git repository
git init
# 3. Create folders for backend and frontend
mkdir backend
mkdir frontend
Now, we'll add a docker-compose.yml file in the root directory. This file tells Docker how to run our PostgreSQL database.
Create docker-compose.yml in the incident-reporter-app/ root folder:
version: '3.8'
services:
db:
image: postgres:14-alpine # Use a trusted PostgreSQL image
restart: always
environment:
- POSTGRES_USER=myuser # The database username
- POSTGRES_PASSWORD=mypassword # The database password
- POSTGRES_DB=incident_db # The database name
ports:
- '5432:5432' # Map port 5432 on your machine to port 5432 in the container
volumes:
- db-data:/var/lib/postgresql/data # Persist database data
volumes:
db-data: # Define the volume for data persistence
Start your database now: Open your terminal in the project root and run:
docker-compose up -d
Your PostgreSQL database is now running in the background!
Phase 2: Building the Backend Foundation (NestJS)
We'll use the NestJS CLI to quickly bootstrap a professional-grade project structure.
-
Navigate into the
backendfolder and create a new NestJS project.cd backend nest new . # The "." creates the project in the current folderWhen prompted to choose a package manager, select npm. This will create a complete NestJS project structure.
-
Install dependencies for database integration. We'll use TypeORM, a powerful Object-Relational Mapper that works wonderfully with NestJS.
npm install @nestjs/typeorm typeorm pg@nestjs/typeorm: The official NestJS module for TypeORM.typeorm: The TypeORM library itself.pg: The driver that allows Node.js to communicate with PostgreSQL.
-
Configure the database connection in
src/app.module.ts. This tells your NestJS application how to find and connect to the Dockerized database.import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', port: 5432, username: 'myuser', // From docker-compose.yml password: 'mypassword', // From docker-compose.yml database: 'incident_db', // From docker-compose.yml entities: [__dirname + '/**/*.entity{.ts,.js}'], // Auto-load entities synchronize: true, // Auto-creates DB tables based on entities. Great for dev, NOT for prod. }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}CRITICAL WARNING:
synchronize: trueis amazing for development as it automatically updates your database schema. However, you must disable it and use migrations in a production environment to avoid potential data loss. -
Generate a "Resource" for incidents. The Nest CLI can generate all the necessary files for a feature (Module, Controller, Service, Entity, DTOs). This is a massive time-saver.
nest generate resource incidents --no-spec # --no-spec skips creating test files for nowChoose REST API when prompted. This command will create a
src/incidentsfolder with all the boilerplate code. -
Define your
IncidentEntity insrc/incidents/entities/incident.entity.ts. This is the TypeORM equivalent of a Django Model. It defines theincidentstable structure.import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'; @Entity() // This marks the class as a database table export class Incident { @PrimaryGeneratedColumn('uuid') // Use UUIDs for primary keys - a good practice id: string; @Column() employee_name: string; @Column({ type: 'timestamp with time zone' }) incident_date: Date; @Column() location: string; @Column('text') description: string; @Column({ type: 'varchar', default: 'open', }) status: 'open' | 'in_progress' | 'closed'; @CreateDateColumn() created_at: Date; } -
Import the
IncidentsModuleinto your rootAppModule. Yournest g resourcecommand should have done this already, but it's good to checksrc/app.module.ts:// src/app.module.ts // ... imports import { IncidentsModule } from './incidents/incidents.module'; @Module({ imports: [ TypeOrmModule.forRoot({ ... }), // Your DB connection IncidentsModule, // Make sure this is here ], //... }) export class AppModule {}
Backend Checkpoint!
- Start the development server:
npm run start:dev - The server should start without errors. Thanks to
synchronize: true, if you check your PostgreSQL database (using a tool like DBeaver or pgAdmin), you will see anincidenttable has been created automatically.
Phase 3: Building the API Endpoints (NestJS)
The resource command already created skeleton files. Now we just fill in the logic.
-
Review the DTO (Data Transfer Object) in
src/incidents/dto/create-incident.dto.ts. This class defines the shape of the data we expect when someone creates a new incident. You can add validation here later. For now, it's a good representation of our entity's fields. -
Implement the Logic in the Service (
src/incidents/incidents.service.ts). The service handles the business logic (interacting with the database).import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateIncidentDto } from './dto/create-incident.dto'; import { Incident } from './entities/incident.entity'; @Injectable() export class IncidentsService { constructor( @InjectRepository(Incident) // Inject the Incident repository private incidentsRepository: Repository<Incident>, ) {} create(createIncidentDto: CreateIncidentDto): Promise<Incident> { const newIncident = this.incidentsRepository.create(createIncidentDto); return this.incidentsRepository.save(newIncident); } findAll(): Promise<Incident[]> { return this.incidentsRepository.find(); } findOne(id: string): Promise<Incident> { return this.incidentsRepository.findOneBy({ id }); } // We will skip update and remove for this initial guide, but they follow a similar pattern } -
Ensure the Controller is Wired Correctly (
src/incidents/incidents.controller.ts). The controller handles incoming HTTP requests and calls the service. Thenest g resourcecommand set this up perfectly. It should already look like this:import { Controller, Get, Post, Body, Param } from '@nestjs/common'; import { IncidentsService } from './incidents.service'; import { CreateIncidentDto } from './dto/create-incident.dto'; @Controller('incidents') // All routes in this controller are prefixed with /incidents export class IncidentsController { constructor(private readonly incidentsService: IncidentsService) {} @Post() // Handles POST /incidents create(@Body() createIncidentDto: CreateIncidentDto) { return this.incidentsService.create(createIncidentDto); } @Get() // Handles GET /incidents findAll() { return this.incidentsService.findAll(); } @Get(':id') // Handles GET /incidents/:id findOne(@Param('id') id: string) { return this.incidentsService.findOne(id); } }
API Test!
- Make sure your NestJS server is running.
- Open Postman/Insomnia.
- Create: Send a
POSTrequest tohttp://localhost:3000/incidentswith a JSON body like:{ "employee_name": "John Doe", "incident_date": "2023-10-27T10:00:00Z", "location": "Warehouse A", "description": "Slipped on a wet floor." } - List: Send a
GETrequest tohttp://localhost:3000/incidents. You should get back an array containing the incident you just created.
- Create: Send a
You have a fully working, well-structured API.
Phase 4 & 5: Frontend (React) & Connection
This part is almost identical to the previous guide, but we'll connect to our NestJS endpoints.
-
Navigate to the
frontenddirectory (in a new terminal!) and create the React app.# (In a new terminal) cd incident-reporter-app/frontend npm create vite@latest- Name it
.and select React and JavaScript.
- Name it
-
Install dependencies and run the app.
npm install npm install axios react-router-dom @mui/material @emotion/react @emotion/styled npm run devYour React app is now running at
http://localhost:5173. -
Enable CORS in the NestJS backend. A web browser will block the React app from talking to the NestJS API unless we explicitly allow it. This is a one-line change.
backend/src/main.ts:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); // <-- ADD THIS LINE await app.listen(3000); } bootstrap();Restart your NestJS server (
npm run start:dev) for the change to take effect. -
Fetch and display data in React. Replace
frontend/src/App.jsxwith code to call your NestJS API.frontend/src/App.jsx:
import { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [incidents, setIncidents] = useState([]); const [error, setError] = useState(null); useEffect(() => { // Fetch incidents from the NestJS API (running on port 3000) axios.get('http://localhost:3000/incidents') // <-- NOTE THE PORT AND ENDPOINT .then(response => { setIncidents(response.data); }) .catch(error => { console.error("There was an error fetching the data!", error); setError("Could not load data. Is the backend server running?"); }); }, []); if (error) return <div>Error: {error}</div>; return ( <div> <h1>Incident Reports</h1> <ul> {incidents.length > 0 ? ( incidents.map(incident => ( <li key={incident.id}> <strong>{incident.location}:</strong> {incident.description} ({incident.status}) </li> )) ) : ( <p>No incidents reported yet. Create one via Postman!</p> )} </ul> </div> ); } export default App;
Grand Finale:
Refresh your React app in the browser (http://localhost:5173). It will now display the list of incidents fetched directly from your NestJS API and PostgreSQL database.
You now have a robust, full-stack foundation ready to be built upon.
Next Steps from Here:
- Implement Validation: Use NestJS's
class-validatorlibrary in your DTOs to automatically validate incoming API requests. - Build Out React Components: Create a proper form for submitting incidents, a details page for viewing one, and use MUI for a professional UI.
- Authentication & Authorization: This is where NestJS excels. Use the
@nestjs/passportand@nestjs/jwtlibraries to implement secure token-based authentication. - Implement RBAC (Role-Based Access Control): Use NestJS Guards to protect endpoints based on user roles (e.g., only a
supervisorcan see all reports). - File Uploads: Implement file uploads to an object store like S3 using NestJS's
MulterModule. - Deploy: Containerize your NestJS and React apps with Docker and deploy them to a HIPAA-compliant cloud provider.