javascriptroom guide

From Zero to Hero: Building a Full-Stack Application with React

In today’s tech landscape, full-stack development is a highly sought-after skill, and React has emerged as the go-to library for building dynamic, user-friendly frontends. But what good is a stunning frontend without a robust backend and database to power it? This blog will take you on a step-by-step journey from "zero" (no prior full-stack experience) to "hero" (building and deploying a fully functional full-stack application). We’ll create a **Task Manager App**—a simple yet powerful project that includes core full-stack concepts: a React frontend for the user interface, a Node.js/Express backend for handling API requests, a MongoDB database for storing data, and deployment to the web. By the end, you’ll understand how these pieces work together and be ready to build your own full-stack projects.

Table of Contents

  1. Prerequisites
  2. Project Overview: What We’ll Build
  3. Setting Up the Frontend with React
  4. Building React Components
  5. Setting Up the Backend with Node.js & Express
  6. Database Integration with MongoDB
  7. Connecting Frontend and Backend
  8. Testing the Application
  9. Deploying the App
  10. Conclusion
  11. References

Prerequisites

Before diving in, ensure you have the following tools installed:

  • Node.js & npm: Download from nodejs.org (includes npm).
  • Code Editor: We’ll use VS Code (free and popular).
  • Git: For version control (optional but recommended).
  • MongoDB Account: Sign up for free at MongoDB Atlas (we’ll use their cloud database).
  • Basic Knowledge: Familiarity with HTML, CSS, JavaScript, and ES6+ syntax (arrow functions, async/await, etc.).

Project Overview: What We’ll Build

Our Task Manager App will let users:

  • Add new tasks with a description.
  • View all tasks in a list.
  • Mark tasks as “complete” (with a strikethrough).
  • Delete tasks.

Architecture:

  • Frontend: React (UI rendering, state management with hooks).
  • Backend: Node.js + Express (REST API endpoints).
  • Database: MongoDB (stores tasks).
  • Deployment: Frontend on Vercel, Backend on Render, Database on MongoDB Atlas.

Setting Up the Frontend with React

Let’s start by creating the React frontend.

Step 1: Create a React App

Open your terminal and run:

npx create-react-app task-manager-frontend  
cd task-manager-frontend  

This creates a new React project with a basic folder structure.

Step 2: Install Dependencies

We’ll need a few libraries:

  • axios: To make API calls to the backend.
  • react-router-dom: For navigation (though our app is simple, it’s good practice).

Install them with:

npm install axios react-router-dom  

Step 3: Project Structure

Clean up the default files (delete logo.svg, App.test.js, etc.) and organize your src folder like this:

src/  
├── components/       # Reusable UI parts (Navbar, TaskForm, TaskList)  
├── pages/            # Full pages (Home)  
├── services/         # API calls to backend  
├── App.js            # Main app component  
├── index.js          # Entry point  
└── .env              # Environment variables (e.g., backend URL)  

Building React Components

Let’s build the core components for our app.

1. Navbar Component

A simple header for the app. Create src/components/Navbar.js:

// src/components/Navbar.js  
import React from 'react';  

const Navbar = () => {  
  return (  
    <nav style={styles.navbar}>  
      <h1>Task Manager</h1>  
    </nav>  
  );  
};  

const styles = {  
  navbar: {  
    background: '#2c3e50',  
    color: 'white',  
    padding: '1rem',  
    textAlign: 'center',  
  },  
};  

export default Navbar;  

2. TaskForm Component

A form to add new tasks. Create src/components/TaskForm.js:

// src/components/TaskForm.js  
import React, { useState } from 'react';  

const TaskForm = ({ addTask }) => {  
  const [description, setDescription] = useState('');  

  const handleSubmit = (e) => {  
    e.preventDefault();  
    if (!description.trim()) return; // Ignore empty tasks  
    addTask(description); // Pass task to parent component  
    setDescription(''); // Clear input  
  };  

  return (  
    <form onSubmit={handleSubmit} style={styles.form}>  
      <input  
        type="text"  
        value={description}  
        onChange={(e) => setDescription(e.target.value)}  
        placeholder="Add a new task..."  
        style={styles.input}  
      />  
      <button type="submit" style={styles.button}>Add Task</button>  
    </form>  
  );  
};  

const styles = {  
  form: {  
    display: 'flex',  
    gap: '0.5rem',  
    margin: '1rem 0',  
  },  
  input: {  
    flex: 1,  
    padding: '0.5rem',  
    fontSize: '1rem',  
  },  
  button: {  
    padding: '0.5rem 1rem',  
    background: '#3498db',  
    color: 'white',  
    border: 'none',  
    borderRadius: '4px',  
    cursor: 'pointer',  
  },  
};  

export default TaskForm;  

3. TaskItem Component

A single task in the list. Create src/components/TaskItem.js:

// src/components/TaskItem.js  
import React from 'react';  

const TaskItem = ({ task, onDelete, onToggleComplete }) => {  
  return (  
    <div style={styles.taskItem}>  
      <input  
        type="checkbox"  
        checked={task.completed}  
        onChange={() => onToggleComplete(task._id)}  
        style={styles.checkbox}  
      />  
      <span style={task.completed ? styles.completed : styles.text}>  
        {task.description}  
      </span>  
      <button  
        onClick={() => onDelete(task._id)}  
        style={styles.deleteButton}  
      >  
        ×  
      </button>  
    </div>  
  );  
};  

const styles = {  
  taskItem: {  
    display: 'flex',  
    alignItems: 'center',  
    gap: '0.5rem',  
    padding: '0.75rem',  
    border: '1px solid #ddd',  
    borderRadius: '4px',  
    margin: '0.5rem 0',  
  },  
  checkbox: {  
    width: '1.2rem',  
    height: '1.2rem',  
  },  
  text: {  
    flex: 1,  
    fontSize: '1rem',  
  },  
  completed: {  
    flex: 1,  
    fontSize: '1rem',  
    textDecoration: 'line-through',  
    color: '#888',  
  },  
  deleteButton: {  
    background: '#e74c3c',  
    color: 'white',  
    border: 'none',  
    borderRadius: '50%',  
    width: '1.5rem',  
    height: '1.5rem',  
    cursor: 'pointer',  
    display: 'flex',  
    alignItems: 'center',  
    justifyContent: 'center',  
  },  
};  

export default TaskItem;  

4. TaskList Component

Displays all tasks. Create src/components/TaskList.js:

// src/components/TaskList.js  
import React from 'react';  
import TaskItem from './TaskItem';  

const TaskList = ({ tasks, onDelete, onToggleComplete }) => {  
  if (tasks.length === 0) {  
    return <p style={styles.emptyMessage}>No tasks yet. Add a task above!</p>;  
  }  

  return (  
    <div>  
      {tasks.map((task) => (  
        <TaskItem  
          key={task._id}  
          task={task}  
          onDelete={onDelete}  
          onToggleComplete={onToggleComplete}  
        />  
      ))}  
    </div>  
  );  
};  

const styles = {  
  emptyMessage: {  
    color: '#888',  
    textAlign: 'center',  
    padding: '1rem',  
  },  
};  

export default TaskList;  

5. Home Page

Combine components into a home page. Create src/pages/Home.js:

// src/pages/Home.js  
import React, { useState, useEffect } from 'react';  
import TaskForm from '../components/TaskForm';  
import TaskList from '../components/TaskList';  
import axios from 'axios';  

const Home = () => {  
  const [tasks, setTasks] = useState([]);  
  const [loading, setLoading] = useState(true);  
  const [error, setError] = useState(null);  

  // Fetch tasks from backend on load  
  useEffect(() => {  
    const fetchTasks = async () => {  
      try {  
        const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/tasks`);  
        setTasks(res.data);  
      } catch (err) {  
        setError('Failed to fetch tasks. Please try again later.');  
        console.error(err);  
      } finally {  
        setLoading(false);  
      }  
    };  

    fetchTasks();  
  }, []);  

  // Add a new task  
  const addTask = async (description) => {  
    try {  
      const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/tasks`, {  
        description,  
      });  
      setTasks([...tasks, res.data]); // Add new task to state  
    } catch (err) {  
      setError('Failed to add task.');  
      console.error(err);  
    }  
  };  

  // Delete a task  
  const deleteTask = async (id) => {  
    try {  
      await axios.delete(`${process.env.REACT_APP_API_URL}/api/tasks/${id}`);  
      setTasks(tasks.filter((task) => task._id !== id)); // Remove from state  
    } catch (err) {  
      setError('Failed to delete task.');  
      console.error(err);  
    }  
  };  

  // Toggle task completion  
  const toggleComplete = async (id) => {  
    try {  
      const taskToUpdate = tasks.find((task) => task._id === id);  
      const res = await axios.patch(`${process.env.REACT_APP_API_URL}/api/tasks/${id}`, {  
        completed: !taskToUpdate.completed,  
      });  
      setTasks(tasks.map((task) => (task._id === id ? res.data : task)));  
    } catch (err) {  
      setError('Failed to update task.');  
      console.error(err);  
    }  
  };  

  if (loading) return <p>Loading tasks...</p>;  
  if (error) return <p style={{ color: 'red' }}>{error}</p>;  

  return (  
    <div style={styles.container}>  
      <h2>My Tasks</h2>  
      <TaskForm addTask={addTask} />  
      <TaskList  
        tasks={tasks}  
        onDelete={deleteTask}  
        onToggleComplete={toggleComplete}  
      />  
    </div>  
  );  
};  

const styles = {  
  container: {  
    maxWidth: '600px',  
    margin: '0 auto',  
    padding: '1rem',  
  },  
};  

export default Home;  

6. Update App.js

Finally, update src/App.js to use the Home page:

// src/App.js  
import React from 'react';  
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';  
import Navbar from './components/Navbar';  
import Home from './pages/Home';  

function App() {  
  return (  
    <Router>  
      <Navbar />  
      <Routes>  
        <Route path="/" element={<Home />} />  
      </Routes>  
    </Router>  
  );  
}  

export default App;  

Setting Up the Backend with Node.js & Express

Now, let’s build the backend API to handle data.

Step 1: Create a Backend Folder

Open a new terminal and run:

mkdir task-manager-backend  
cd task-manager-backend  
npm init -y  

Step 2: Install Backend Dependencies

npm install express mongoose cors dotenv  
npm install --save-dev nodemon  
  • express: Web framework for Node.js.
  • mongoose: ODM for MongoDB (simplifies database interactions).
  • cors: Handles Cross-Origin Resource Sharing (allows frontend to call the backend).
  • dotenv: Manages environment variables.
  • nodemon: Auto-restarts the server during development.

Step 3: Configure Environment Variables

Create a .env file in the backend folder:

PORT=5000  
MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/taskmanager?retryWrites=true&w=majority  

Replace <username> and <password> with your MongoDB Atlas credentials (we’ll set this up next).

Step 4: Create the Server

Create server.js:

// server.js  
require('dotenv').config();  
const express = require('express');  
const mongoose = require('mongoose');  
const cors = require('cors');  

const app = express();  

// Middleware  
app.use(cors()); // Allow cross-origin requests  
app.use(express.json()); // Parse JSON bodies  

// Connect to MongoDB  
mongoose  
  .connect(process.env.MONGODB_URI)  
  .then(() => console.log('Connected to MongoDB'))  
  .catch((err) => console.error('MongoDB connection error:', err));  

// Basic route  
app.get('/', (req, res) => {  
  res.send('Task Manager API is running!');  
});  

// Start server  
const PORT = process.env.PORT || 5000;  
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));  

Database Integration with MongoDB

Step 1: Set Up MongoDB Atlas

  1. Go to MongoDB Atlas and sign up.
  2. Create a new cluster (free tier is fine).
  3. Under “Database Access”, add a user (username/password).
  4. Under “Network Access”, allow access from anywhere (temporarily for development).
  5. Click “Connect” → “Connect your application” → Copy the connection string.
  6. Replace <username> and <password> in your backend .env file with the user credentials.

Step 2: Create a Task Model

Create models/Task.js in the backend folder:

// models/Task.js  
const mongoose = require('mongoose');  

const taskSchema = new mongoose.Schema({  
  description: {  
    type: String,  
    required: true,  
    trim: true,  
  },  
  completed: {  
    type: Boolean,  
    default: false,  
  },  
  createdAt: {  
    type: Date,  
    default: Date.now,  
  },  
});  

module.exports = mongoose.model('Task', taskSchema);  

Step 3: Add API Routes

Create routes/tasks.js:

// routes/tasks.js  
const express = require('express');  
const router = express.Router();  
const Task = require('../models/Task');  

// GET all tasks  
router.get('/', async (req, res) => {  
  try {  
    const tasks = await Task.find().sort({ createdAt: -1 }); // Newest first  
    res.json(tasks);  
  } catch (err) {  
    res.status(500).json({ error: 'Server error' });  
  }  
});  

// POST a new task  
router.post('/', async (req, res) => {  
  try {  
    const newTask = new Task({  
      description: req.body.description,  
    });  
    const task = await newTask.save();  
    res.json(task);  
  } catch (err) {  
    res.status(400).json({ error: 'Invalid task data' });  
  }  
});  

// PATCH (update) a task's completion status  
router.patch('/:id', async (req, res) => {  
  try {  
    const task = await Task.findById(req.params.id);  
    if (!task) return res.status(404).json({ error: 'Task not found' });  

    task.completed = req.body.completed;  
    await task.save();  
    res.json(task);  
  } catch (err) {  
    res.status(500).json({ error: 'Server error' });  
  }  
});  

// DELETE a task  
router.delete('/:id', async (req, res) => {  
  try {  
    const task = await Task.findByIdAndDelete(req.params.id);  
    if (!task) return res.status(404).json({ error: 'Task not found' });  
    res.json({ message: 'Task deleted' });  
  } catch (err) {  
    res.status(500).json({ error: 'Server error' });  
  }  
});  

module.exports = router;  

Step 4: Update Server.js to Use Routes

Add this to server.js (after connecting to MongoDB):

// Import routes  
const taskRoutes = require('./routes/tasks');  
app.use('/api/tasks', taskRoutes); // All task routes start with /api/tasks  

Step 5: Update package.json for Nodemon

Add a script to package.json to run the server with nodemon:

"scripts": {  
  "start": "node server.js",  
  "dev": "nodemon server.js"  
}  

Connecting Frontend and Backend

Step 1: Set Up Frontend Environment Variable

Create a .env file in the frontend folder:

REACT_APP_API_URL=http://localhost:5000  

(We’ll update this to the deployed backend URL later.)

Step 2: Run Both Servers

  • Backend: In the backend terminal, run npm run dev (starts on port 5000).
  • Frontend: In the frontend terminal, run npm start (starts on port 3000).

Testing the Application

  1. Open http://localhost:3000 in your browser.
  2. Add a task (e.g., “Learn React”).
  3. Check if it appears in the list.
  4. Mark it as complete (strikethrough should appear).
  5. Delete the task.

If everything works, your full-stack app is functioning!

Deployment

1. Deploy the Frontend (Vercel)

  1. Push your frontend code to GitHub.
  2. Go to Vercel and sign up with GitHub.
  3. Import your frontend repository.
  4. Set the environment variable REACT_APP_API_URL to your deployed backend URL (we’ll get this next).
  5. Click “Deploy”.

2. Deploy the Backend (Render)

  1. Push your backend code to GitHub.
  2. Go to Render and sign up.
  3. Create a new “Web Service”.
  4. Import your backend repository.
  5. Set:
    • Build Command: npm install
    • Start Command: npm start
    • Environment Variables: Add MONGODB_URI (your Atlas connection string).
  6. Click “Create Web Service”. Copy the deployed URL (e.g., https://task-manager-backend.onrender.com).

3. Update Frontend Environment Variable

Go back to Vercel, update REACT_APP_API_URL to your Render backend URL, and redeploy.

Conclusion

Congratulations! You’ve built a full-stack application with React, Node.js, Express, and MongoDB—from scratch! You’ve learned:

  • How to structure a React app with components and hooks.
  • How to build a REST API with Express.
  • How to connect a database (MongoDB) to the backend.
  • How to deploy both frontend and backend.

Next steps to expand your app:

  • Add user authentication (JWT, OAuth).
  • Implement state management with Redux or Context API.
  • Add due dates or categories to tasks.
  • Style with a UI library like Material-UI or Tailwind CSS.

References