Deploying a Full-Stack Notes App with Node.js and React

Som palkarSom palkar
8 min read

In the landscape of contemporary web development, full-stack applications represent the pinnacle of integrated software solutions, seamlessly bridging client-side interactivity with server-side logic. The synergy of Node.js, a JavaScript runtime renowned for its event-driven, non-blocking I/O model, and React, a declarative library for crafting component-based user interfaces, has become a de facto standard for building scalable and responsive web applications. When hosted on Ubuntu, a robust and developer-friendly Linux distribution, this stack leverages a stable environment conducive to rapid development and deployment.

This comprehensive guide elucidates the process of constructing a full-stack notes application using Node.js for the backend and React for the frontend, all within an Ubuntu environment. The application will enable users to create, view, and delete notes, demonstrating RESTful API design, component-based architecture, and end-to-end integration. We will incorporate advanced concepts such as static code analysis, containerization, security best practices, and performance optimization, ensuring an intellectually rigorous and practically applicable tutorial. The guide assumes familiarity with JavaScript, basic web development, and Ubuntu command-line operations.

Prerequisites

To embark on this journey, ensure the following:

  • An Ubuntu 22.04 (or later) system, accessible via terminal.

  • Basic knowledge of JavaScript, Node.js, React, and REST APIs.

  • A GitHub account for version control (optional but recommended).

  • Installed tools:

    • Node.js and npm for runtime and package management.

    • Git for version control.

    • Docker (optional) for containerized deployment.

Step 1: Setting Up the Development Environment

A robust development environment is the foundation of any full-stack project. Ubuntu’s package management and developer tools make it an ideal choice.

1.1 Installing Node.js and npm

Node.js powers the backend, while npm manages dependencies. To ensure access to a recent version, use the NodeSource PPA:

  • Update Package Index:

      sudo apt update
    
  • Install Dependencies:

      sudo apt install -y curl gnupg
    
  • Add NodeSource Signing Key:

      curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
    
  • Add NodeSource Repository (for Node.js 20.x):

      echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
    
  • Update and Install Node.js:

      sudo apt update
      sudo apt install -y nodejs
    
  • Verify Installation:

      node -v
      npm -v
    

This installs Node.js 20.x and npm, suitable for modern development.

1.2 Installing Additional Tools

  • Git: Essential for version control and collaboration:

      sudo apt install -y git
    
  • PostgreSQL (Optional): For persistent data storage:

      sudo apt install -y postgresql postgresql-contrib
    
  • Docker (Optional): For containerized deployment:

      sudo apt install -y docker.io
      sudo usermod -aG docker $USER
      sudo systemctl enable docker
      sudo systemctl start docker
    

1.3 Project Structure

Create a project directory and initialize version control:

mkdir notes-app
cd notes-app
git init

The structure will be:

notes-app/
├── backend/
│   ├── src/
│   ├── package.json
│   └── Dockerfile
├── frontend/
│   ├── src/
│   ├── package.json
│   └── Dockerfile
└── README.md

Step 2: Building the Backend with Node.js and Express.js

The backend will expose RESTful APIs to manage notes, leveraging Express.js, a minimalist web framework for Node.js that simplifies routing and middleware integration.

2.1 Initializing the Backend

Create and navigate to the backend directory:

mkdir backend
cd backend
npm init -y

Install dependencies:

npm install express cors
  • Express.js: Handles HTTP requests and routing.

  • cors: Enables Cross-Origin Resource Sharing for frontend-backend communication.

2.2 Creating the Server

Create src/index.js:

const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());

let notes = [
  { id: 1, title: 'Sample Note', content: 'This is a sample note.' }
];

app.get('/api/notes', (req, res) => {
  res.json(notes);
});

app.post('/api/notes', (req, res) => {
  const { title, content } = req.body;
  if (!title || !content) {
    return res.status(400).json({ error: 'Title and content are required' });
  }
  const newNote = { id: notes.length + 1, title, content };
  notes.push(newNote);
  res.status(201).json(newNote);
});

app.delete('/api/notes/:id', (req, res) => {
  const id = parseInt(req.params.id);
  notes = notes.filter(note => note.id !== id);
  res.status(204).send();
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

This implements:

  • GET /api/notes: Retrieves all notes.

  • POST /api/notes: Creates a new note with validation.

  • DELETE /api/notes/:id: Deletes a note by ID.

2.3 Running the Backend

Start the server:

node src/index.js

Test the API using curl or a tool like Postman:

curl http://localhost:3000/api/notes

2.4 Dockerizing the Backend (Optional)

Create a Dockerfile in the backend directory:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]

Build and run the Docker image:

docker build -t notes-backend .
docker run -p 3000:3000 notes-backend

Step 3: Building the Frontend with React

The frontend will be a single-page application (SPA) built with React, utilizing functional components and hooks for state management and side effects.

3.1 Creating the React App

Navigate to the project root and create the frontend:

cd ..
npx create-react-app frontend
cd frontend

Install Tailwind CSS for styling:

npm install -D tailwindcss
npx tailwindcss init

Configure tailwind.config.js:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: { extend: {} },
  plugins: [],
};

Add Tailwind directives to src/index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

3.2 Creating the Notes Component

Replace src/App.js with:

import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [notes, setNotes] = useState([]);
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  useEffect(() => {
    fetch('http://localhost:3000/api/notes')
      .then(res => res.json())
      .then(data => setNotes(data))
      .catch(err => console.error('Error fetching notes:', err));
  }, []);

  const handleAddNote = async () => {
    if (!title || !content) return;
    const res = await fetch('http://localhost:3000/api/notes', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title, content }),
    });
    const newNote = await res.json();
    setNotes([...notes, newNote]);
    setTitle('');
    setContent('');
  };

  const handleDeleteNote = async (id) => {
    await fetch(`http://localhost:3000/api/notes/${id}`, { method: 'DELETE' });
    setNotes(notes.filter(note => note.id !== id));
  };

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">Notes App</h1>
      <div className="mb-4">
        <input
          type="text"
          placeholder="Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          className="border p-2 mr-2"
        />
        <textarea
          placeholder="Content"
          value={content}
          onChange={(e) => setContent(e.target.value)}
          className="border p-2"
        />
        <button
          onClick={handleAddNote}
          className="bg-blue-500 text-white p-2 ml-2"
        >
          Add Note
        </button>
      </div>
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        {notes.map(note => (
          <div key={note.id} className="border p-4">
            <h2 className="text-xl">{note.title}</h2>
            <p>{note.content}</p>
            <button
              onClick={() => handleDeleteNote(note.id)}
              className="bg-red-500 text-white p-1 mt-2"
            >
              Delete
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

export default App;

3.3 Running the Frontend

Start the React app:

npm start

Access `[invalid url, do not cite] to interact with the notes app.

3.4 Dockerizing the Frontend (Optional)

Create a Dockerfile in the frontend directory:

FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Build and run:

docker build -t notes-frontend .
docker run -p 80:80 notes-frontend

Step 4: Integrating Frontend and Backend

To integrate, ensure the frontend can communicate with the backend. In development, the React app runs on port 3001, and the backend on 3000, requiring CORS. In production, serve the React build from the Node.js server.

4.1 Production Integration

In backend/src/index.js, add:

const path = require('path');
app.use(express.static(path.join(__dirname, '../../frontend/build')));
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '../../frontend/build', 'index.html'));
});

Build the frontend:

cd frontend
npm run build

Run the backend to serve both:

cd ../backend
node src/index.js

Access `[invalid url, do not cite] to see the integrated app.

Step 5: Advanced Topics

5.1 Security

  • Authentication: Implement JWT or OAuth for user authentication using libraries like passport.

  • Input Validation: Use express-validator to sanitize inputs, preventing SQL injection or XSS.

  • HTTPS: Deploy with SSL/TLS using Let’s Encrypt.

5.2 Performance Optimization

  • Caching: Use Redis for caching API responses.

  • Code Splitting: Implement React.lazy for dynamic imports.

  • Compression: Enable gzip compression in Express:

      const compression = require('compression');
      app.use(compression());
    

5.3 Testing

  • Unit Testing: Use Jest for backend and frontend tests.

  • Integration Testing: Test APIs with Supertest.

  • End-to-End Testing: Use Cypress for full-stack testing.

5.4 Monitoring and Logging

  • Logging: Use Winston for structured logging:

      const winston = require('winston');
      const logger = winston.createLogger({
        transports: [new winston.transports.Console()],
      });
    
  • Monitoring: Integrate Prometheus for metrics collection.

Step 6: Deployment

For production, deploy using Docker and optionally Kubernetes.

6.1 Docker Deployment

Run both containers:

docker run -d -p 3000:3000 notes-backend
docker run -d -p 80:80 notes-frontend

6.2 Kubernetes Deployment (Optional)

Create a Kubernetes deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: notes-backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: notes-backend
  template:
    metadata:
      labels:
        app: notes-backend
    spec:
      containers:
      - name: notes-backend
        image: notes-backend:latest
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: notes-backend
spec:
  selector:
    app: notes-backend
  ports:
  - port: 3000
    targetPort: 3000

Apply:

kubectl apply -f deployment.yaml

Troubleshooting

IssueSolution
Backend not respondingVerify server is running (node src/index.js) and port 3000 is open.
Frontend CORS errorsEnsure cors middleware is enabled in Express.
Docker build failsCheck Dockerfile syntax and ensure dependencies are installed.

Conclusion

This guide has demonstrated the construction of a full-stack notes application using Node.js and React on Ubuntu. By setting up a robust development environment, building a RESTful backend, creating an interactive frontend, and integrating them with advanced practices, developers can create scalable and maintainable applications. Further enhancements include integrating a database like PostgreSQL, implementing CI/CD pipelines, or exploring serverless architectures.

0
Subscribe to my newsletter

Read articles from Som palkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Som palkar
Som palkar