Github

Building a Simple MERN Stack Application from Scratch

Github

The MERN stack—MongoDB, Express, React, and Node.js—is a powerful and popular stack for building full-stack JavaScript applications. It allows developers to use a single language, JavaScript, across the entire stack, which streamlines development and makes learning easier. In this guide, we’ll go through building a simple MERN stack application from scratch.


Introduction

Creating a full-stack application from scratch might seem daunting, but with the MERN stack, you can set up and structure your project quickly, using JavaScript for both front-end and back-end code. This guide will walk you through building a basic CRUD application using MongoDB as the database, Express as the backend framework, React for the front end, and Node.js as the server environment.

Prerequisites

Ensure you have Node.js and MongoDB installed on your machine. Basic knowledge of JavaScript and the fundamentals of these frameworks is helpful but not required.


Step 1: Setting Up the Backend with Node.js and Express

Initialize a new Node.js project in your chosen directory:

bash

mkdir mern-tutorial
cd mern-tutorial
npm init -y

Install Express and Mongoose, which we’ll use to connect to MongoDB and manage our API routes.

tsx

npm install express mongoose cors

Set up the Express server in a new file called server.ts:

tsx

import express, { Request, Response } from 'express';
import mongoose from 'mongoose';
import cors from 'cors';

const app = express();
const PORT = 5000;

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

mongoose.connect('mongodb://localhost:27017/mernapp', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
}).then(() => console.log('Connected to MongoDB'));

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

Define a Model for your data using Mongoose. Let’s say we’re creating a simple to-do app:

tsx

import mongoose, { Document } from 'mongoose';

interface ITodo extends Document {
  title: string;
  completed: boolean;
}

const TodoSchema = new mongoose.Schema({
  title: { type: String, required: true },
  completed: { type: Boolean, default: false },
});

export const Todo = mongoose.model<ITodo>('Todo', TodoSchema);

Set up routes to create, read, update, and delete to-do items. Add the following routes to server.ts:

tsx

app.post('/todos', async (req: Request, res: Response) => {
  const { title } = req.body;
  const newTodo = new Todo({ title });
  await newTodo.save();
  res.json(newTodo);
});

app.get('/todos', async (req: Request, res: Response) => {
  const todos = await Todo.find();
  res.json(todos);
});

app.put('/todos/:id', async (req: Request, res: Response) => {
  const { id } = req.params;
  const updatedTodo = await Todo.findByIdAndUpdate(id, req.body, { new: true });
  res.json(updatedTodo);
});

app.delete('/todos/:id', async (req: Request, res: Response) => {
  const { id } = req.params;
  await Todo.findByIdAndDelete(id);
  res.json({ message: 'Todo deleted' });
});

Step 2: Setting Up the Frontend with React

Create a new React project in a separate directory using Create React App:

scss

npx create-react-app client --template typescript
cd client
npm install axios

Create a service for API calls using Axios in a new file called api.ts:

tsx

import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:5000',
});

export const fetchTodos = () => api.get('/todos');
export const createTodo = (todo: { title: string }) => api.post('/todos', todo);
export const updateTodo = (id: string, updatedTodo: { completed: boolean }) => api.put(`/todos/${id}`, updatedTodo);
export const deleteTodo = (id: string) => api.delete(`/todos/${id}`);

Build a simple component to display the list of to-dos and allow for creating new to-dos. In App.tsx:

tsx

import React, { useEffect, useState } from 'react';
import { fetchTodos, createTodo, updateTodo, deleteTodo } from './api';

interface Todo {
  _id: string;
  title: string;
  completed: boolean;
}

const App: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [title, setTitle] = useState('');

  useEffect(() => {
    const getTodos = async () => {
      const { data } = await fetchTodos();
      setTodos(data);
    };
    getTodos();
  }, []);

  const addTodo = async () => {
    const { data } = await createTodo({ title });
    setTodos([...todos, data]);
    setTitle('');
  };

  const toggleComplete = async (id: string) => {
    const todo = todos.find(todo => todo._id === id);
    if (todo) {
      const { data } = await updateTodo(id, { completed: !todo.completed });
      setTodos(todos.map(todo => (todo._id === id ? data : todo)));
    }
  };

  const removeTodo = async (id: string) => {
    await deleteTodo(id);
    setTodos(todos.filter(todo => todo._id !== id));
  };

  return (
    <div>
      <h1>To-Do List</h1>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="New to-do"
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo._id}>
            <span
              style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
              onClick={() => toggleComplete(todo._id)}
            >
              {todo.title}
            </span>
            <button onClick={() => removeTodo(todo._id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;

Step 3: Connecting Frontend and Backend

  1. Start both servers:

    • Run npm start in the server folder to start the backend.

    • Run npm start in the client folder to start the frontend.

  2. Enable CORS in the backend (already added in Step 1) to allow the frontend to make requests.

  3. Test your application: You should be able to add, complete, and delete to-do items, and have the data persist through page refreshes.


Conclusion

Building a simple MERN stack application gives you a solid foundation for creating full-stack applications. This stack's versatility allows you to scale your projects while using a single language, JavaScript. Try adding more features like authentication, pagination, or search functionality to further improve and expand your app!