Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Lavkush-Singh34/31c6a5f307dfdaf73796a52a6894f735 to your computer and use it in GitHub Desktop.
Save Lavkush-Singh34/31c6a5f307dfdaf73796a52a6894f735 to your computer and use it in GitHub Desktop.

mern-project-template Code-Along Guide

Today's plan to code a mern-project-template that includes JWT authentication and can be used to start any MERN-Stack project, including Project 3!

Initial Project Setup

  1. Create a folder named mern-project-template within the ~/code/ga folder.

  2. cd into the new mern-project-template folder and open it in VS Code.

  3. Open a Terminal in VS Code.

  4. Run npm init -y to create a package.json file.

  5. To make the integrated project deployable on Heroku, update the "scripts" in the package.json to:

    "scripts": {
      "start": "node ./backend/server.js",
      "build": "npm install && npm --prefix ./frontend install  && npm --prefix ./frontend run build"
    },
  6. Update the "main" script to reference the yet created server.js:

    "main": "./backend/server.js",
  7. Create a React frontend project:

    npm create vite@latest frontend
    
    • If prompted to install any packages, answer y
    • When prompted to select a framework, choose React
    • When prompted to select a variant, choose JavaScript
  8. cd into the newly created frontend folder and

    • npm i to install the Node modules for the React frontend
    • npm run dev to start the React dev server
    • Browse to localhost:5173
  9. Add to the rules within eslint.config.js (last two entries):

     rules: {
    
     ...
    
     'react/prop-types': 'off',
     'react/no-unescaped-entities': 'off',
    },
  10. During development, the fetch requests from the React app will be sent to the React Dev Server, however, those fetch requests are intended for the Express server. The following update to vite.config.js will proxy (forward) requests beginning with /api to the Express backend running on localhost:3000:

    export default defineConfig({
      plugins: [react()],
      server: {
        proxy: {
          '/api': 'http://localhost:3000',
        },
      },
    });
  11. Delete the frontend/README.md or move it to the root of the project.

  12. Open another Terminal in VS Code (it takes two Terminal sessions to develop in the MERN-Stack) and ensure that the working directory is the project root.

  13. Since there are two terminal sessions required, consider renaming and updating their color, e.g.:

    • Rename the React terminal (npm run dev) to "React" and update its color to cyan.
    • Rename the new terminal to "Express" and update its color to green.
  14. Create a .env file in the root of the project and add your Atlas/MongoDB database connection string, e.g.:

    MONGODB_URI=mongodb+srv://seb:[email protected]/mern-project-template?retryWrites=true&w=majority&appName=Cluster0
    
  15. Change the database name to something like mern-project-template.

  16. Create a folder named backend to be used for the Express server code. The new folder should appear right above the frontend folder in VS Code's Explorer.

  17. Create an empty server.js module within the backend folder.

Code the Initial Express Server Code

  1. Install the initial set of Node modules for the backend:

    npm i express dotenv morgan mongoose
    
  2. Code a minimal server.js

    πŸ‘€ Click to View Code
    const path = require('path'); // Built into Node
    const express = require('express');
    const logger = require('morgan');
    const app = express();
    
    // Process the secrets/config vars in .env
    require('dotenv').config();
    
    // Connect to the database
    require('./db');
    
    app.use(logger('dev'));
    // Serve static assets from the frontend's built code folder (dist)
    app.use(express.static(path.join(__dirname, '../frontend/dist')));
    // Note that express.urlencoded middleware is not needed
    // because forms are not submitted!
    app.use(express.json());
    
    // API Routes
    
    // Use a "catch-all" route to deliver the frontend's production index.html
    app.get('*', function (req, res) {
      res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
    });
    
    const port = process.env.PORT || 3000;
    app.listen(port, () => {
      console.log(`The express app is listening on ${port}`);
    });
  3. Create a backend/db.js module that server.js is using to connect to the database. Add the familiar code to connect to the database.

    πŸ‘€ Click to View Code
    const mongoose = require('mongoose');
    
    mongoose.connect(process.env.MONGODB_URI);
    
    mongoose.connection.on('connected', () => {
      console.log(`Connected to MongoDB ${mongoose.connection.name}`);
    });
  4. Note that the Express server has a "catch-all" route used to deliver the frontend's production index.html when the app is browsed to after it's deployed (also at localhost:3000 - but we only want to browse to localhost:5173 during development). Express will throw an error if the ../frontend/dist/index.html file does not exist. Let's use the build script to create it:

    npm run build
    
  5. Let's now start the Express server for development using the familiar nodemon. You should see the following messages:

    The express app is listening on 3000
    Connected to MongoDB mern-project-template
    
  6. If you browse to localhost:3000, you should see the "Vite + React" page. However, this is the built/production React frontend and again, you don't want to browse localhost:3000 during development - so close the tab!

Implement JWT Authentication

Add user State and Set Up Client-Side Routing with Some Skeleton Page-Level Components

  1. Cleanup the code in App.jsx:

    πŸ‘€ Click to View Code
    import { useState } from 'react';
    import './App.css';
    
    export default function App() {
      return (
        <main className="App">
          <section id="main-section">In progress...</section>
        </main>
      );
    }
  2. Update App.css to the following CSS:

    πŸ‘€ Click to View CSS
    .App {
      display: grid;
      grid-template-rows: auto auto;
    }
    
    #main-section {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
  3. Update index.css to the following CSS:

    πŸ‘€ Click to View CSS
    :root {
      font-family: Arial, Helvetica, sans-serif;
      line-height: 1.5;
      font-weight: 400;
    
      color: #1a1a1a;
      background-color: whitesmoke;
    
      font-synthesis: none;
      text-rendering: optimizeLegibility;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    
    * {
      box-sizing: border-box;
    }
    
    body {
      margin: 0;
      min-height: 100vh;
    }
    
    h1,
    h2 {
      text-align: center;
    }
    
    button {
      border: 0.5vmin solid #1a1a1a;
      border-radius: 0.5vmin;
      padding: 0.8vmin;
      font-size: 2vmin;
      font-weight: 500;
      font-family: inherit;
      background-color: #1a1a1a;
      color: whitesmoke;
      cursor: pointer;
    }
    
    button:hover {
      background-color: white;
      color: #1a1a1a;
      border-color: #1a1a1a;
    }
    
    button:disabled {
      background-color: grey;
    }
    
    form {
      display: grid;
      gap: 1.2vmin;
      padding: 4vmin;
      border: 0.5vmin solid #1a1a1a;
      border-radius: 1vmin;
    }
    
    input,
    select,
    textarea {
      padding: 0.7vmin;
      font-size: 2vmin;
      border: 0.3vmin solid #1a1a1a;
      border-radius: 0.5vmin;
    }
    
    form > button:last-child {
      margin-top: 2vmin;
    }
    
    label {
      font-size: 2vmin;
      font-weight: bold;
    }
    
    h2 {
      text-align: center;
    }
    
    .error-message {
      color: rgb(166, 29, 29);
    }
  4. Open a third Terminal session and cd into the frontend folder.

  5. Create a components and a pages folder within the frontend/src folder.

  6. For better organization, create an App folder in the pages and move the App.* files into it. This change requires that the import in main.jsx be updated.

  7. Add user state - initialize to null for now.

  8. Install react-router.

  9. In main.jsx import { BrowserRouter as Router } and wrap <App /> with it.

    πŸ‘€ Click to View Code
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import { BrowserRouter as Router } from 'react-router';
    import './index.css';
    import App from './pages/App/App.jsx';
    
    createRoot(document.getElementById('root')).render(
      <StrictMode>
        <Router>
          <App />
        </Router>
      </StrictMode>
    );
  10. Import react-routers Routes & Route components in App.jsx.

  11. Update App.jsx to include two blocks of <Routes>, one for when there's a user logged in, the other when not:

    πŸ‘€ Click to View Code
    <section id="main-section">
      {user ? (
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/posts" element={<PostListPage />} />
          <Route path="/posts/new" element={<NewPostPage />} />
        </Routes>
      ) : (
        <Routes>
          <Route path="/" element={<HomePage />} />
        </Routes>
      )}
    </section>
  12. We're going to need the <HomePage>, <PostListPage> & <NewPostPage> page-level components (components that are rendered by <Route> components) we just referenced. Be sure to create them in their own folder within the pages folder and import them in App.jsx:

    πŸ‘€ Click to View Code

    Create basic components that simply render a basic page title...

    export default function HomePage() {
      return <h1>Home Page</h1>;
    }
  13. Create a <NavBar> component that renders dynamically based on user state. Note that <NavLink> components will add an active class to the link if it's currently browsed to:

    πŸ‘€ Click to View Code
    import { NavLink, Link } from 'react-router';
    import './NavBar.css';
    
    export default function NavBar({ user }) {
      return (
        <nav className="NavBar">
          <NavLink to="/">Home</NavLink>
          &nbsp; | &nbsp;
          {user ? (
            <>
              <NavLink to="/posts" end>
                Post List
              </NavLink>
              &nbsp; | &nbsp;
              <NavLink to="/posts/new">New Post</NavLink>
              &nbsp; | &nbsp;
              {/* TODO: Add Log Out Link */}
              <span>Welcome, {user.name}</span>
            </>
          ) : (
            <>
              <NavLink to="/login">Log In</NavLink>
              &nbsp; | &nbsp;
              <NavLink to="/signup">Sign Up</NavLink>
            </>
          )}
        </nav>
      );
    }
  14. We need to add that NavBar.css that's being imported:

    πŸ‘€ Click to View CSS
    .NavBar {
      display: flex;
      justify-content: space-around;
      align-items: center;
      padding: 2vmin;
      font-size: 2vmin;
    }
    
    .NavBar {
      a {
        border-radius: 0.8vmin;
        padding: 0.5vmin 1.5vmin;
        text-decoration: none;
        color: #1a1a1a;
      }
    
      a:hover {
        background-color: #1a1a1a;
        color: whitesmoke;
      }
    
      a.active {
        background-color: rebeccapurple;
        color: whitesmoke;
      }
    
      span {
        color: rgb(108, 9, 87);
      }
    }

Implement Sign-Up Functionality

AAV, I want to click the [Sign Up] link in the nav to see a Sign Up form and sign up so that I can access the app's functionality it has to offer.

  1. Be sure that you're in the root of the project and install the bcrypt library used to hash passwords.

  2. Create a User model that automatically hashes the password using bcrypt:

    πŸ‘€ Click to View Code
    const mongoose = require('mongoose');
    const Schema = mongoose.Schema;
    const bcrypt = require('bcrypt');
    
    const SALT_ROUNDS = 6;
    
    const userSchema = new Schema(
      {
        name: { type: String, required: true },
        email: {
          type: String,
          unique: true,
          trim: true,
          lowercase: true,
          required: true,
        },
        password: {
          type: String,
          required: true,
        },
      },
      {
        timestamps: true,
        // Remove password when doc is sent across network
        toJSON: {
          transform: function (doc, ret) {
            delete ret.password;
            return ret;
          },
        },
      }
    );
    
    userSchema.pre('save', async function (next) {
      // 'this' is the user document
      if (!this.isModified('password')) return next();
      // Replace the password with the computed hash
      this.password = await bcrypt.hash(this.password, SALT_ROUNDS);
    });
    
    module.exports = mongoose.model('User', userSchema);

Begin With the Frontend (React) Then Follow the Code Flow to the Server and Back

  1. Create a <SignUpPage> component with controlled inputs and formState containing name, email, password & confirm properties. Also add a separate errorMsg state variable:

    πŸ‘€ Click to View Code
    import { useState } from 'react';
    import { useNavigate } from 'react-router';
    
    export default function SignUpPage() {
      const [formData, setFormData] = useState({
        name: '',
        email: '',
        password: '',
        confirm: '',
      });
      const [errorMsg, setErrorMsg] = useState('');
       
      const navigate = useNavigate();
    
      function handleChange(evt) {
        setFormData({ ...formData, [evt.target.name]: evt.target.value });
        setErrorMsg('');
      }
    
      const disable = formData.password !== formData.confirm;
    
      return (
        <>
          <h2>Sign Up!</h2>
          <form autoComplete="off">
            <label>Name</label>
            <input
              type="text"
              name="name"
              value={formData.name}
              onChange={handleChange}
              required
            />
            <label>Email</label>
            <input
              type="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              required
            />
            <label>Password</label>
            <input
              type="password"
              name="password"
              value={formData.password}
              onChange={handleChange}
              required
            />
            <label>Confirm</label>
            <input
              type="password"
              name="confirm"
              value={formData.confirm}
              onChange={handleChange}
              required
            />
            <button type="submit" disabled={disable}>
              SIGN UP
            </button>
          </form>
          <p className="error-message">&nbsp;{errorMsg}</p>
        </>
      );
    }
  2. Add a <Route> in App.jsx to handle when the "Sign Up" link is clicked and renders the <SignUpPage> component.

    πŸ‘€ Click to View Code
    ...
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/signup" element={<SignUpPage />} />
    </Routes>
  3. We're going to need an authService.js responsible for communicating with the Express backend when signing up and logging in. Create a services/authService.js module in the src folder.

  4. Discuss token-based authentication using JWTs (JSON Web Tokens).

  5. Before we start coding a signUp method in authService.js let's consider how much similar fetch code was repeated when we CRUD'd pets in the petService. Now consider that there's additional code needed to send the logged in user's token with each request. Finally, consider that very similar code would be repeated for each additional data resource. Yep, not very DRY. When you have the same or similar code being repeated, it's a candidate for refactoring into a reusable function. Create a src/services/sendRequest.js module and let's code this mighty code saver:

    πŸ‘€ Click to View Code
    import { getToken } from './authService';
    
    export default async function sendRequest(
      url,
      method = 'GET',
      payload = null
    ) {
      // Fetch accepts an options object as the 2nd argument
      // used to include a data payload, set headers, specifiy the method, etc.
      const options = { method };
      // If payload is a FormData object (used to upload files),
      // fetch will automatically set the Content-Type to 'multipart/form-data',
      // otherwise set the Content-Type header as usual
      if (payload instanceof FormData) {
        options.body = payload;
      } else if (payload) {
        options.headers = { 'Content-Type': 'application/json' };
        options.body = JSON.stringify(payload);
      }
      const token = getToken();
      if (token) {
        // Need to add an Authorization header
        // Use the Logical OR Assignment operator
        options.headers ||= {};
        // Older approach
        // options.headers = options.headers || {};
        options.headers.Authorization = `Bearer ${token}`;
      }
      const res = await fetch(url, options);
      // if res.ok is false then something went wrong
      if (res.ok) return res.json();
      // Obtain error sent from server
      const err = await res.json();
      // Throw error to be handled in React
      throw new Error(err.message);
    }
  6. sendRequest() depends upon a getToken() method that retrieves the JWT from local storage and verifies that it isn't expired:

    πŸ‘€ Click to View Code
    export function getToken() {
      // getItem returns null if there's no key
      const token = localStorage.getItem('token');
      if (!token) return null;
      const payload = JSON.parse(atob(token.split('.')[1]));
      // A JWT's exp is expressed in seconds, not milliseconds, so convert
      if (payload.exp * 1000 < Date.now()) {
        localStorage.removeItem('token');
        return null;
      }
      return token;
    }
  7. Now's a good time to code the signUp function in the authService:

    πŸ‘€ Click to View Code
    import sendRequest from './sendRequest';
    
    const BASE_URL = '/api/auth';
    
    export async function signUp(userData) {
      const token = await sendRequest(`${BASE_URL}/signup`, 'POST', userData);
      localStorage.setItem('token', token);
      // Return the user object from the token to component
      return getUser();
    }
    
    export function getUser() {
      const token = getToken();
      return token ? JSON.parse(atob(token.split('.')[1])).user : null;
    }
    
    export function getToken() {
      // getItem returns null if there's no key
      const token = localStorage.getItem('token');
      if (!token) return null;
      const payload = JSON.parse(atob(token.split('.')[1]));
      // A JWT's exp is expressed in seconds, not milliseconds, so convert
      if (payload.exp * 1000 < Date.now()) {
        localStorage.removeItem('token');
        return null;
      }
      return token;
    }
  8. Let's import the authService in SignUpPage.jsx and call the signUp function when the [SIGN UP] button is clicked.

    πŸ‘€ Click to View Code
     import { useState } from 'react';
     import * as authService from '../../services/authService';
    
     export default function SignUpPage() {
    
       ...
    
       async function handleSubmit(evt) {
         evt.preventDefault();
         try {
           const user = await authService.signUp(formData);
           setUser(user);
           navigate('/posts');
         } catch (err) {
           console.log(err);
           setErrorMsg('Sign Up Failed - Try Again');
         }
       }
    
       ...
    
       <form autoComplete="off" onSubmit={handleSubmit}>
  9. Looks like <SignUpPage> needs access to the setUser function - you got this!

The Frontend is Sending an HTTP To the Server - To the Backend We Go!

  1. We'll get a nice "Sign Up Failed - Try Again" error if we try to sign up because the server doesn't have routing or controller code yet! Create the following modules:

    • backend/routes/auth.js
    • backend/controllers/auth.js
  2. In routes/auth.js we'll only define the routes and use controller functions that are defined in controllers/auth.js. This is a best practice way to organize code in Express. Let's define a route to handle sign up:

    πŸ‘€ Click to View Code
    const express = require('express');
    const router = express.Router();
    const authCtrl = require('../controllers/auth');
    
    // All paths start with '/api/auth'
    
    // POST /api/auth/signup
    router.post('/signup', authCtrl.signUp);
    
    module.exports = router;
  3. It's so easy to forget to mount new routers in server.js! Mount routes/auth.js to a starts with path of /api/auth.

  4. The Express app now shows an error because the route is expecting a signUp function, but we're not exporting one from the controller yet. If we stub one up, we'll see the error go away:

    πŸ‘€ Click to View Code
    const User = require('../models/user');
    
    module.exports = {
      signUp,
    };
    
    async function signUp(req, res) {}
  5. Before we can create a JWT in the signUp function, we need make sure that we're in the root of the project then npm i jsonwebtoken.

  6. We're also going to need to add a SECRET to the .env that will be used to "sign" the JWT:

    MONGODB_URI=mongodb+srv://seb:[email protected]/mern-project-template?retryWrites=true&w=majority&appName=Cluster0
    SECRET=SEBRocks
    
  7. Now we can code the signUp controller function to create the user document and return a JWT:

    πŸ‘€ Click to View Code
    const User = require('../models/user');
    const jwt = require('jsonwebtoken');
    
    module.exports = {
      signUp,
    };
    
    async function signUp(req, res) {
      try {
        const user = await User.create(req.body);
        const token = createJWT(user);
        res.json(token);
      } catch (err) {
        res.status(400).json({ message: 'Duplicate Email' });
      }
    }
    
    /*--- Helper Functions ---*/
    
    function createJWT(user) {
      return jwt.sign(
        // data payload
        { user },
        process.env.SECRET,
        { expiresIn: '24h' }
      );
    }
  8. Bingo! If you get an error, be sure to use a unique email address that hasn't been used. If successful, you can observe the JWT in local storage using Chrome DevTool's Application tab.

Set user State Upon Load or Refresh

If we refresh the page, the user state will be initialize back to null despite the fact that there's a valid JWT in localStorage. We can resolve this issue by using the authService.getUser() function to initialize the user state in App.jsx. Let's import just the getUser() function:

import { getUser } from '../../services/authService';

and put it to work!

const [user, setUser] = useState(getUser());

Implement Logging Out

Logging out is just a matter of:

  • Delete the JWT from local storage
  • Set user state to null
  1. Starting with the UI, let's add a [Log Out] link in <NavBar>:

    πŸ‘€ Click to View Code
     &nbsp; | &nbsp;
     <a onClick={handleLogOut}>Log Out</a>
     &nbsp; | &nbsp;
     <span>Welcome, {user.name}</span>
  2. Here's that handleLogOut() function:

    πŸ‘€ Click to View Code
    // Add useNavigate
    import { NavLink, Link, useNavigate } from 'react-router';
    
    ...
        
    const navigate = useNavigate();
        
    function handleLogOut(evt) {
      evt.preventDefault();
      authService.logOut();
      setUser(null);
      navigate('/');
    }
  3. Looking at handleLogOut, what else do we need to do?

  4. Finish logging out functionality by coding a simple logOut() function in authService.js:

    πŸ‘€ Click to View Code
    export function logOut() {
      localStorage.removeItem('token');
    }

Tired of Signing Up? Let's Implement Log In Functionality

Logging in is very much like signing up, so let's rock this...

  1. Here's the <LogInPage> component - a review of the code will show that it's very similar to the <SignUpPage>:

    πŸ‘€ Click to View Code
    import { useState } from 'react';
    import { useNavigate } from 'react-router';
    import * as authService from '../../services/authService';
    
    export default function LogInPage({ setUser }) {
      const [formData, setFormData] = useState({
        email: '',
        password: '',
      });
      const [errorMsg, setErrorMsg] = useState('');
      
      const navigate = useNavigate();
    
      async function handleSubmit(evt) {
        evt.preventDefault();
        try {
          const user = await authService.logIn(formData);
          setUser(user);
          navigate('/posts');
        } catch (err) {
          setErrorMsg('Log In Failed - Try Again');
        }
      }
    
      function handleChange(evt) {
        setFormData({ ...formData, [evt.target.name]: evt.target.value });
        setErrorMsg('');
      }
    
      return (
        <>
          <h2>Log In!</h2>
          <form autoComplete="off" onSubmit={handleSubmit}>
            <label>Email</label>
            <input
              type="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              required
            />
            <label>Password</label>
            <input
              type="password"
              name="password"
              value={formData.password}
              onChange={handleChange}
              required
            />
            <button type="submit">LOG IN</button>
          </form>
          <p className="error-message">&nbsp;{errorMsg}</p>
        </>
      );
    }
  2. Add the <Route> in App.jsx to match the "Log In" link.

  3. Now we need the authService.logIn() function:

    πŸ‘€ Click to View Code
    export async function logIn(credentials) {
      const token = await sendRequest(`${BASE_URL}/login`, 'POST', credentials);
      localStorage.setItem('token', token);
      return getUser();
    }
  4. Okay, we're sending the HTTP request to the server. Now what? Try not to peek πŸ˜„:

    πŸ‘€ Click to View Code
     // routes/auth.js
    
     ...
    
     // POST /api/auth/login
     router.post('/login', authCtrl.logIn);
  5. The Express server will crash until we code the login() controller function:

    πŸ‘€ Click to View Code
    async function logIn(req, res) {
      try {
        const user = await User.findOne({ email: req.body.email });
        if (!user) throw new Error();
        const match = await bcrypt.compare(req.body.password, user.password);
        if (!match) throw new Error();
        const token = createJWT(user);
        res.json(token);
      } catch (err) {
        res.status(400).json({ message: 'Bad Credentials' });
      }
    }
  6. Try logging in. Congrats!

Verify Tokens Sent to the Server and Assign the user's Doc to req.user

Yep, custom middleware to the rescue!

  1. Create a backend/middleware/checkToken.js module with the following code:

    πŸ‘€ Click to View Code
    const jwt = require('jsonwebtoken');
    
    module.exports = function (req, res, next) {
      // Check for the token being sent in a header or as a query param
      let token = req.get('Authorization') || req.query.token;
      // Default to null
      req.user = null;
      if (!token) return next();
      // Remove the 'Bearer ' that was included in the token header
      token = token.replace('Bearer ', '');
      // Check if token is valid and not expired
      jwt.verify(token, process.env.SECRET, function (err, decoded) {
        // Invalid token if err
        if (err) return next();
        // decoded is the entire token payload
        req.user = decoded.user;
        return next();
      });
    };
  2. Mount that middleware in server.js:

    πŸ‘€ Click to View Code
     ...
    
     app.use(express.json());
    
     // Middleware to check the request's headers for a JWT and
     // verify that it's a valid.  If so, it will assign the
     // user object in the JWT's payload to req.user
     app.use(require('./middleware/checkToken'));

One last thing, as always, remember to protect routes in your app that require a logged in...

Protect Routes on the Server With ensureLoggedIn Middleware

  1. Add a middleware/ensureLoggedIn.js module and add the following somewhat familiar bit of code:

    πŸ‘€ Click to View Code
    module.exports = function (req, res, next) {
      if (!req.user) return res.status(401).json({ message: 'Unauthorized' });
      next();
    };
  2. Now you can protect all routes in a router in server.js like this:

    // API Routes
    app.use('/api/auth', require('./routes/auth'));
    
    // Routers mounted below ensureLoggedIn middleware
    // protects all routes defined in that router
    app.use(require('./middleware/ensureLoggedIn'));
    // All routes in routes/posts.js will be protected
    // app.use('/api/posts', require('./routes/posts'));

    Or protect individual routes within routers like this:

    const express = require('express');
    const router = express.Router();
    const postsCtrl = require('../controllers/posts');
    const ensureLoggedIn = require('../middleware/ensureLoggedIn');
    
    // All paths start with '/api/posts'
    
    // GET /api/posts
    router.post('/', ensureLoggedIn, postsCtrl.index);

Congrats!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment