# Building a Full-Stack MERN Authentication App: Backend Development

### Introduction:

We are going to build a full-stack MERN (MongoDB, Express.js, React.js, Node.js) authentication app. The app will include user registration, login, authentication, and profile management features. In this first part of the tutorial, we will focus on the backend development using Node.js and Express.js. Let's get started!

### Prerequisites:

To follow along with this, you should have a basic understanding of JavaScript, Node.js, and Express.js. Make sure you have Node.js and npm (Node Package Manager) installed on your machine.

#### Setting up the Project:

1. Create a new directory for your project and navigate into it using the command line.
    
2. Initialize a new Node.js project by running the following command:
    
    ```javascript
    npm init -y
    ```
    
3. Install the required dependencies by executing the following command:
    
    ```javascript
    npm install express mongoose bcryptjs jsonwebtoken cookie-parser dotenv
    ```
    
    This will install Express.js, Mongoose (for MongoDB integration), bcryptjs (for password hashing), jsonwebtoken (for generating and verifying tokens), cookie-parser (for handling cookies), and dotenv (for environment variable configuration).
    

#### Connecting to MongoDB:

1. Create a new file named `db.js` in the project's root directory.
    
2. Open the `db.js` file and add the following code:
    
    ```javascript
    import mongoose from 'mongoose';
    
    const connectDB = async () => {
      try {
        const conn = await mongoose.connect(process.env.MONGO_URI);
        console.log(`MongoDB Connected: ${conn.connection.host}`);
      } catch (error) {
        console.error(`Error: ${error.message}`);
        process.exit(1);
      }
    };
    
    export default connectDB;
    ```
    
3. This code establishes a connection to the MongoDB database specified by the `MONGO_URI` environment variable.
    

#### Creating the user model:

1. Create a new folder named `models` in the project's root directory.
    
2. Inside the `models` folder, create a file named `UserModel.js`.
    
3. Open the `UserModel.js` file and add the following code:
    
    ```javascript
    import mongoose from 'mongoose';
    import bcrypt from 'bcryptjs';
    
    const userSchema = mongoose.Schema(
      {
        name: {
          type: String,
          required: true,
        },
        email: {
          type: String,
          required: true,
          unique: true,
        },
        password: {
          type: String,
          required: true,
        },
      },
      {
        timestamps: true,
      }
    );
    
    // Match user entered password to hashed password in the database
    userSchema.methods.matchPassword = async function (enteredPassword) {
      return await bcrypt.compare(enteredPassword, this.password);
    };
    
    // Encrypt password using bcrypt before saving the user
    userSchema.pre('save', async function (next) {
      if (!this.isModified('password')) {
        next();
      }
    
      const salt = await bcrypt.genSalt(10);
      this.password = await bcrypt.hash(this.password, salt);
    });
    
    const User = mongoose.model('User', userSchema);
    
    export default User;
    ```
    
4. This code defines the user schema and model using Mongoose. It includes fields for name, email, and password. It also includes methods for comparing passwords and encrypting the password before saving.
    

#### Creating the user Routes and Controllers:

1. Create a new folder named `controllers` in the project's root directory.
    
2. Inside the `controllers` folder, create a file named `userController.js`.
    
3. Open the `userController.js` file and add the following code:
    
    ```javascript
    import jwt from 'jsonwebtoken';
    import asyncHandler from 'express-async-handler';
    import User from '../models/UserModel.js';
    
    // Authenticate user and generate token
    const authUser = asyncHandler(async (req, res) => {
      const { email, password } = req.body;
    
      const user = await User.findOne({ email });
    
      if (user && (await user.matchPassword(password))) {
        const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, {
          expiresIn: '30d',
        });
    
        res.cookie('jwt', token, {
          httpOnly: true,
          secure: process.env.NODE_ENV !== 'development',
          sameSite: 'strict',
          maxAge: 31557600000, // 1 year
        });
    
        res.json({
          _id: user._id,
          name: user.name,
          email: user.email,
        });
      } else {
        res.status(401);
        throw new Error('Invalid email or password');
      }
    });
    
    // Register a new user
    const registerUser = asyncHandler(async (req, res) => {
      const { name, email, password } = req.body;
    
      const userExists = await User.findOne({ email });
    
      if (userExists) {
        res.status(400);
        throw new Error('User already exists');
      }
    
      const user = await User.create({
        name,
        email,
        password,
      });
    
      if (user) {
        const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, {
          expiresIn: '30d',
        });
    
        res.cookie('jwt', token, {
          httpOnly: true,
          secure: process.env.NODE_ENV !== 'development',
          sameSite: 'strict',
          maxAge: 31557600000, // 1 year
        });
    
        res.status(201).json({
          _id: user._id,
          name: user.name,
          email: user.email,
        });
      } else {
        res.status(400);
        throw new Error('Invalid user data');
      }
    });
    
    // Logout user and clear cookie
    const logoutUser = (req, res) => {
      res.cookie('jwt', '', {
        httpOnly: true,
        expires: new Date(0),
      });
      res.status(200).json({ message: 'Logged out successfully' });
    };
    
    // Get user profile
    const getUserProfile = asyncHandler(async (req, res) => {
      const user = await User.findById(req.user._id);
    
      if (user) {
        res.json({
          _id: user._id,
          name: user.name,
          email: user.email,
        });
      } else {
        res.status(404);
        throw new Error('User not found');
      }
    });
    
    // Update user profile
    const updateUserProfile = asyncHandler(async (req, res) => {
      const user = await User.findById(req.user._id);
    
      if (user) {
        user.name = req.body.name || user.name;
        user.email = req.body.email || user.email;
    
        if (req.body.password) {
          user.password = req.body.password;
        }
    
        const updatedUser = await user.save();
    
        res.json({
          _id: updatedUser._id,
          name: updatedUser.name,
          email: updatedUser.email,
        });
      } else {
        res.status(404);
        throw new Error('User not found');
      }
    });
    
    export { authUser, registerUser, logoutUser, getUserProfile, updateUserProfile };
    ```
    
4. These controller function handle user authentication, registration, logout, profile management operations (UPDATE).
    

#### Creating Authentication Middleware:

1. Create a new folder named `middleware` in the project's root directory.
    
2. Inside the `middleware` folder, create a file named `authMiddleware.js`.
    
3. Open the `authMiddleware.js` file and add the following code:
    

```javascript
import jwt from 'jsonwebtoken';
import asyncHandler from 'express-async-handler';
import User from '../models/UserModel.js';

// Middleware to protect routes
const protect = asyncHandler(async (req, res, next) => {
  let token;

  token = req.cookies.jwt;

  if (token) {
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);

      req.user = await User.findById(decoded.userId).select('-password');

      next();
    } catch (error) {
      console.error(error);
      res.status(401);
      throw new Error('Not authorized, token failed');
    }
  } else {
    res.status(401);
    throw new Error('Not authorized, no token');
  }
});

export { protect };
```

1. This middleware function verifies the token from the request's cookies and sets the authenticated user in the `req.user` object.
    

#### Error Handling Middleware:

1. Create a new file named `errorMiddleware.js` inside the `middleware` folder.
    
2. Open the `errorMiddleware.js` file and add the following code:
    
    ```javascript
    const notFound = (req, res, next) => {
      const error = new Error(`Not Found - ${req.originalUrl}`);
      res.status(404);
      next(error);
    };
    
    const errorHandler = (err, req, res, next) => {
      let statusCode = res.statusCode === 200 ? 500 : res.statusCode;
      let message = err.message;
    
      // If Mongoose not found error, set to 404 and change message
      if (err.name === 'CastError' && err.kind === 'ObjectId') {
        statusCode = 404;
        message = 'Resource not found';
      }
    
      res.status(statusCode).json({
        message: message,
        stack: process.env.NODE_ENV === 'production' ? null : err.stack,
      });
    };
    
    export { notFound, errorHandler };
    ```
    
3. These middleware functions handle 404 errors and general error handling for the application.
    

#### Setting up the Server:

1. Create a new file named `server.js` in the project's root directory.
    
2. Open the `server.js` file and add the following code:
    
    ```javascript
    import path from 'path';
    import express from 'express';
    import dotenv from 'dotenv';
    import connectDB from './config/db.js';
    import cookieParser from 'cookie-parser';
    import { notFound, errorHandler } from './middleware/errorMiddleware.js';
    import userRoutes from './routes/userRoutes.js';
    
    const port = process.env.PORT || 5000;
    
    connectDB();
    
    const app = express();
    
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));
    
    app.use(cookieParser());
    
    app.use('/api/users', userRoutes);
    
    if (process.env.NODE_ENV === 'production') {
      const __dirname = path.resolve();
      app.use(express.static(path.join(__dirname, '/frontend/dist')));
    
      app.get('*', (req, res) =>
        res.sendFile(path.resolve(__dirname, 'frontend', 'dist', 'index.html'))
      );
    } else {
      app.get('/', (req, res) => {
        res.send('API is running....');
      });
    }
    
    app.use(notFound);
    app.use(errorHandler);
    
    app.listen(port, () => console.log(`Server started on port ${port}`));
    ```
    
3. This code sets up the Express.js server, connects to the MongoDB database, and defines the routes and error-handling middleware.
    

#### Conclusion

In this first part of the tutorial, we have built the backend of our MERN authentication app. We have established a connection to the MongoDB database, created the user model, and implemented user authentication, registration, profile management, and error-handling functionalities. In the next part of the tutorial, we will move on to front-end development using React.js.

Stay tuned for the next part of the tutorial where we will cover the frontend development using React.js. In the meantime, you can test the backend by running the server and making HTTP requests to the provided routes.

Happy Coding !
