Add NestJS.md
This commit is contained in:
363
NestJS.md
Normal file
363
NestJS.md
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
1. **Node.js and npm:** (Node LTS version). This is required for both the frontend and backend. [Download Node.js](https://nodejs.org/)
|
||||||
|
2. **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](https://www.docker.com/products/docker-desktop/)
|
||||||
|
3. **NestJS CLI:** The command-line interface for creating and managing NestJS projects.
|
||||||
|
```bash
|
||||||
|
npm i -g @nestjs/cli
|
||||||
|
```
|
||||||
|
4. **Git:** For version control. [Download Git](https://git-scm.com/downloads)
|
||||||
|
5. **A Code Editor:** **VS Code** is highly recommended due to its excellent TypeScript support.
|
||||||
|
6. **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.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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:**
|
||||||
|
```yaml
|
||||||
|
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:
|
||||||
|
```bash
|
||||||
|
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.
|
||||||
|
|
||||||
|
1. **Navigate into the `backend` folder and create a new NestJS project.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
nest new . # The "." creates the project in the current folder
|
||||||
|
```
|
||||||
|
When prompted to choose a package manager, select **npm**. This will create a complete NestJS project structure.
|
||||||
|
|
||||||
|
2. **Install dependencies for database integration.** We'll use **TypeORM**, a powerful Object-Relational Mapper that works wonderfully with NestJS.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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.
|
||||||
|
|
||||||
|
3. **Configure the database connection in `src/app.module.ts`.** This tells your NestJS application how to find and connect to the Dockerized database.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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: true` is 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.
|
||||||
|
|
||||||
|
4. **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.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nest generate resource incidents --no-spec # --no-spec skips creating test files for now
|
||||||
|
```
|
||||||
|
Choose **REST API** when prompted. This command will create a `src/incidents` folder with all the boilerplate code.
|
||||||
|
|
||||||
|
5. **Define your `Incident` Entity in `src/incidents/entities/incident.entity.ts`.** This is the TypeORM equivalent of a Django Model. It defines the `incidents` table structure.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Import the `IncidentsModule` into your root `AppModule`**. Your `nest g resource` command should have done this already, but it's good to check `src/app.module.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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 an `incident` table 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.
|
||||||
|
|
||||||
|
1. **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.
|
||||||
|
|
||||||
|
2. **Implement the Logic in the Service (`src/incidents/incidents.service.ts`).** The service handles the business logic (interacting with the database).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ensure the Controller is Wired Correctly (`src/incidents/incidents.controller.ts`).** The controller handles incoming HTTP requests and calls the service. The `nest g resource` command set this up perfectly. It should already look like this:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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 `POST` request to `http://localhost:3000/incidents` with a JSON body like:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"employee_name": "John Doe",
|
||||||
|
"incident_date": "2023-10-27T10:00:00Z",
|
||||||
|
"location": "Warehouse A",
|
||||||
|
"description": "Slipped on a wet floor."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* **List:** Send a `GET` request to `http://localhost:3000/incidents`. You should get back an array containing the incident you just created.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
1. **Navigate to the `frontend` directory (in a new terminal!) and create the React app.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# (In a new terminal)
|
||||||
|
cd incident-reporter-app/frontend
|
||||||
|
npm create vite@latest
|
||||||
|
```
|
||||||
|
* Name it `.` and select **React** and **JavaScript**.
|
||||||
|
|
||||||
|
2. **Install dependencies and run the app.**
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm install axios react-router-dom @mui/material @emotion/react @emotion/styled
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
Your React app is now running at `http://localhost:5173`.
|
||||||
|
|
||||||
|
3. **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:**
|
||||||
|
```typescript
|
||||||
|
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.
|
||||||
|
|
||||||
|
4. **Fetch and display data in React.** Replace `frontend/src/App.jsx` with code to call your NestJS API.
|
||||||
|
|
||||||
|
**frontend/src/App.jsx:**
|
||||||
|
```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:
|
||||||
|
|
||||||
|
1. **Implement Validation:** Use NestJS's `class-validator` library in your DTOs to automatically validate incoming API requests.
|
||||||
|
2. **Build Out React Components:** Create a proper form for submitting incidents, a details page for viewing one, and use MUI for a professional UI.
|
||||||
|
3. **Authentication & Authorization:** This is where NestJS excels. Use the `@nestjs/passport` and `@nestjs/jwt` libraries to implement secure token-based authentication.
|
||||||
|
4. **Implement RBAC (Role-Based Access Control):** Use NestJS **Guards** to protect endpoints based on user roles (e.g., only a `supervisor` can see all reports).
|
||||||
|
5. **File Uploads:** Implement file uploads to an object store like S3 using NestJS's `MulterModule`.
|
||||||
|
6. **Deploy:** Containerize your NestJS and React apps with Docker and deploy them to a HIPAA-compliant cloud provider.
|
||||||
Reference in New Issue
Block a user