Building an Authenticated MERN Stack App with Material-UI (Archived)
Note: This article was published in 2018 and is now outdated.
This tutorial uses older versions of the MERN stack components and tools like
create-react-app. While the core concepts of building a full-stack application with authentication remain relevant, the specific implementation details, dependencies, and setup process have changed significantly. This content is preserved for historical purposes.
Building a full-stack MERN (MongoDB, Express, React, Node.js) application involves integrating a back-end server with a client-side user interface. While connecting the server and database is a foundational step, creating a functional and visually appealing UI can be a significant challenge.
This tutorial provides a step-by-step guide to building a small, authenticated web application using the MERN stack. It also demonstrates how to integrate the Material-UI library to create a polished and consistent user interface.
Prerequisites
Before starting, ensure you have the following tools installed:
- Node.js
- MongoDB
yarnornpmcreate-react-app(globally or vianpx)
Initial Project Setup
First, create a project directory and initialize the server-side application.
# Create a project directory and navigate into it
mkdir mern-material-demo
cd mern-material-demo
# Initialize a Node.js project
npm init -y
# Install server-side dependencies
yarn add express mongoose cookie-parser express-jwt jsonwebtoken
# Install Babel for modern JavaScript syntax on the server
yarn add -D babel-cli babel-preset-env babel-watch
# Create the client-side React application
create-react-app client
This setup creates a monorepo structure with a server (our Node.js app) and a client (our React app).
Configure Babel by adding a dev script to your root package.json:
"scripts": {
"dev": "babel-watch index.js"
}
Server and Database Configuration
Create a server/config/index.js file to manage configuration variables.
const config = {
port: process.env.PORT || 4000,
jwtSecret: process.env.JWT_SECRET || 'mkT23j#u!45',
mongoURI: process.env.MONGODB_URI || 'mongodb://localhost/mern-auth'
};
export default config;
Next, set up the database connection in server/config/dbConnection.js using Mongoose.
import mongoose from 'mongoose';
import config from './index';
mongoose.connect(config.mongoURI);
mongoose.connection.on('connected', () => {
console.log('Established Mongoose Default Connection');
});
mongoose.connection.on('error', err => {
console.log('Mongoose Default Connection Error : ' + err);
});
Create a basic Express server in the root index.js file to tie everything together.
import express from 'express';
import cookieParser from 'cookie-parser';
import config from './server/config';
// Establish DB connection
require('./server/config/dbConnection');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// Error handling middleware
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
res.status(401).json({ error: err.name + ':' + err.message });
}
});
app.listen(config.port, () => {
console.log(` at port ${config.port}`);
});
Running yarn dev should now start your server and connect to MongoDB.
Building the User Model and Auth Logic
The user model defines the schema for user data in MongoDB. Create server/models/user.js and define the schema using Mongoose. This includes fields for name, email, and a hashedPassword with salt. A virtual password field is used to handle encryption logic without storing the plain-text password.
The authentication logic, including sign-in, sign-out, and route protection middleware, is defined in server/controllers/auth.js. This file will use jsonwebtoken to create and manage JWTs for authenticating users.
User-related operations like registration, profile retrieval, and deletion are handled in server/controllers/user.js.
API Routes
The API routes connect the controller logic to specific endpoints. User routes are defined in server/routes/user.js and authentication routes in server/routes/auth.js. These are then imported and used in the main index.js server file.
Protected routes will use middleware like requireSignin and hasAuthorization to ensure only authenticated and authorized users can access them.
Setting Up the React Client with Material-UI
Navigate to the client directory to set up the front-end. Install Material-UI and its dependencies:
yarn add @material-ui/core @material-ui/icons
In public/index.html, add the Roboto font from Google Fonts, which is recommended for Material-UI.
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />
In src/App.js, wrap the application with MuiThemeProvider to inject a custom theme. This allows you to define a consistent color palette and typography.
import React from 'react';
import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';
import { createMuiTheme } from '@material-ui/core/styles';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Routes';
const theme = createMuiTheme({
palette: {
// ... your theme configuration
type: 'dark'
}
});
function App() {
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<Routes />
</MuiThemeProvider>
</BrowserRouter>
);
}
export default App;
Client-Side Routing and Components
Use react-router-dom to manage client-side navigation. Create a src/Routes.js file to define the application's routes, including public routes like home, sign-in, and sign-up, as well as private routes for authenticated users.
- Home Component: A simple landing page using Material-UI's
Cardcomponents. - Authentication Components:
Signup.jsandSignin.jswill be forms built with Material-UI'sTextFieldandButtoncomponents.auth-helper.jswill contain functions to manage the JWT in session storage.PrivateRoute.jsis a higher-order component that checks if a user is authenticated before rendering a protected component.
- User Components:
Profile.jsdisplays user information fetched from the server.DeleteUser.jsprovides functionality to delete a user's account.
- Navbar Component: A navigation bar built with Material-UI's
AppBarandToolbarthat displays different links based on the user's authentication status.
Connecting the Client and Server
To allow the React development server to proxy API requests to the Node.js back-end, add a proxy field to the client/package.json file.
"proxy": "http://localhost:4000/"
Create utility files (api-user.js and api-auth.js) to encapsulate fetch calls to your back-end API endpoints for signing in, signing up, and managing user profiles.
Running the Full Application
With both the server and client configured, run them concurrently in separate terminal windows:
# In the root directory
yarn dev
# In the client directory
yarn start
You can now navigate the application, sign up for a new account, sign in, view your profile, and sign out.
Conclusion
This tutorial outlined the process of building a complete MERN stack application with an authenticated API and a front-end built with React and Material-UI. While some of the tools and specific versions are dated, the fundamental architecture—a separate back-end API serving a client-side SPA—remains a common and powerful pattern for modern web development.