๐Ÿš€ MERN Stack Deployment: From Development to Production

๐Ÿ“š On this page
๐Ÿš€ MERN Stack Deployment: From Development to Production

Hey there, awesome developers! ๐Ÿ‘‹ Today, weโ€™re diving into the world of MERN stack deployment. Whether youโ€™re a coding newbie or a seasoned pro, this guide will walk you through setting up your project for both development and production. Letโ€™s get started!

#1. ๐Ÿ—๏ธ Project Setup

Our MERN (MongoDB, Express, React, Node.js) stack project has two main parts:

  • ๐Ÿ“‚ server: This is where our backend lives.
  • ๐Ÿ“‚ client: Home to our React frontend.

Hereโ€™s the correct project structure:

my-mern-app/
โ”œโ”€โ”€ server/
โ”‚   โ””โ”€โ”€ server.js
โ”œโ”€โ”€ client/
โ”‚   โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ package.json
โ”‚   โ””โ”€โ”€ vite.config.js
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ .env
Copied!

Note that we have a package.json file in the root of the project and another one in the client directory. This setup allows us to manage dependencies and scripts for both the server and client from the project root.

#2. ๐Ÿ› ๏ธ Development Environment

In development, we run our backend and frontend separately. This setup allows for hot reloading and easier debugging.

#Backend (Express Server)

In server/server.js:

lang
server/server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 5000;
 
app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello from the server! ๐Ÿ‘‹' });
});
 
app.listen(PORT, () => console.log(`Server running on port ${PORT} ๐Ÿš€`));
Copied!

#Frontend (React with Vite)

In client/vite.config.js:

lang
client/vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': 'http://localhost:5000',
    },
  },
});
Copied!

This config sets up a proxy, so when your React app makes a request to /api, itโ€™s forwarded to your Express server. Itโ€™s like having a tiny mailman for your development setup! ๐Ÿ“ฎ

#Root package.json

In the root package.json, add these scripts:

lang
package.json
{
  "scripts": {
    "dev": "nodemon server/server.js",
    "start": "node server/server.js",
    "build": "npm install && npm install --prefix client && npm run build --prefix client"
  }
}
Copied!

These scripts allow you to:

  • Run the server in development mode with hot reloading (npm run dev)
  • Start the server in production mode (npm start)
  • Build the entire project, including installing dependencies and building the client (npm run build)

#3. ๐ŸŒ Production Environment

Hereโ€™s where the magic happens! In production, we serve everything from a single domain. No need for separate deployments for API and client. Itโ€™s like a one-stop-shop for your app! ๐Ÿช

Update your server/server.js:

lang
server/server.js
const express = require('express');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 5000;
 
app.use(express.json());
 
app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello from the server! ๐Ÿ‘‹' });
});
 
if (process.env.NODE_ENV === 'production') {
  app.use(express.static(path.join(__dirname, '../client/dist')));
  app.get('*', (req, res) => {
    res.sendFile(path.resolve(__dirname, '../client', 'dist', 'index.html'));
  });
}
 
app.listen(PORT, () => console.log(`Server running on port ${PORT} ๐Ÿš€`));
Copied!

This setup serves your React app and handles all routes in production. Itโ€™s like a smart butler for your app! ๐Ÿง๐Ÿฝ๏ธ

#4. ๐Ÿช„ The Magical Deploy Script

Before we dive into the deploy script, you need to create it in your project. You have two options:

  1. Create a new file named deploy.sh in the root of your project and copy the script content into it.

  2. Or, you can download the script directly using curl:

curl -o deploy.sh https://gist.githubusercontent.com/mhdZhHan/5a18d763a92b8ad8eb08f9a74cc7625a/raw/be7448bbbfd0992f82673386a868d25b4cecbbb9/deploy.sh
Copied!

After downloading or creating the file, make sure to make it executable:

chmod +x deploy.sh
Copied!

Now, letโ€™s look at our deploy script. Itโ€™s like a Swiss Army knife for your deployment! ๐Ÿ‡จ๐Ÿ‡ญ๐Ÿ”ช

lang
deploy.sh
#!/bin/bash
 
# ๐ŸŽญ Ask for the star of our show
echo "What's your domain name, superstar? (e.g., example.com):"
read DOMAIN_NAME
 
# ๐Ÿ”ง Update and install our toolkit
echo "Time to upgrade our toolbox! ๐Ÿงฐ"
sudo apt update -y && sudo apt upgrade -y
sudo apt install -y nginx certbot python3-certbot-nginx curl software-properties-common
 
# ๐ŸŒฑ Plant the Node.js seed
echo "Planting the Node.js tree! ๐ŸŒณ"
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install pm2 -g
 
# ๐Ÿ”‘ Secret keeper (.env file handler)
setup_env() {
    echo "Let's set up our secret codes! ๐Ÿ•ต๏ธโ€โ™€๏ธ"
    local env_file=".env"
    local default_vars=("MONGODB_URL" "JWT_SECRET" "NODE_ENV" "PORT")
    
    if [ -f "$env_file" ]; then
        echo "Existing .env file found. Updating values..."
        source "$env_file"
    else
        echo "Creating new .env file..."
    fi
    
    for var in "${default_vars[@]}"; do
        read -p "Enter $var (current: ${!var}): " new_value
        if [ ! -z "$new_value" ]; then
            eval "$var='$new_value'"
        fi
    done
    
    # Prompt for additional variables
    while true; do
        read -p "Add another environment variable? (y/n): " add_more
        if [[ $add_more != "y" ]]; then
            break
        fi
        read -p "Enter variable name: " var_name
        read -p "Enter variable value: " var_value
        eval "$var_name='$var_value'"
    done
    
    # Write to .env file
    > "$env_file"
    for var in $(compgen -v); do
        if [[ ! "$var" =~ ^(BASH|HOSTNAME|HOME|PWD|SHELL|USER|_).*$ ]]; then
            echo "$var=\"${!var}\"" >> "$env_file"
        fi
    done
    
    echo ".env file updated successfully. ๐ŸŽ‰"
}
 
# Call the setup_env function
setup_env
 
# ๐Ÿ—๏ธ Building our masterpiece
echo "Time to build our Lego set! ๐Ÿงฑ"
npm install
npm run build
 
# ๐Ÿšฆ Traffic director (Nginx setup)
setup_nginx() {
    echo "Setting up our traffic signs for $DOMAIN_NAME ๐Ÿšฆ"
    local config_path="/etc/nginx/sites-available/$DOMAIN_NAME"
    
    if [ -f "$config_path" ]; then
        echo "Nginx configuration for $DOMAIN_NAME already exists. Updating..."
        sudo rm "$config_path"
    fi
    
    sudo tee "$config_path" > /dev/null <<EOF
server {
    listen 80;
    server_name $DOMAIN_NAME;
 
    location / {
        proxy_pass http://localhost:$PORT;
        proxy_http_version 1.1;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host \$host;
        proxy_cache_bypass \$http_upgrade;
    }
}
EOF
    
    sudo ln -sf "$config_path" "/etc/nginx/sites-enabled/"
    sudo nginx -t && sudo systemctl reload nginx
    
    echo "Nginx configured for $DOMAIN_NAME ๐Ÿšฆ"
}
 
# Call the setup_nginx function
setup_nginx
 
# ๐Ÿ”’ Security guard (SSL setup)
echo "Calling our security team! ๐Ÿ›ก๏ธ"
sudo certbot --nginx -d "$DOMAIN_NAME"
 
# Start the app with PM2
echo "Launching our rocket! ๐Ÿš€"
pm2 start npm --name "$DOMAIN_NAME" -- start
 
# ๐Ÿงฑ Firewall (the bouncer at our app's nightclub)
echo "Setting up the velvet rope ๐Ÿ”ฅ๐Ÿงฑ"
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
 
echo "๐ŸŽ‰ Woohoo! Your app is now live at https://$DOMAIN_NAME ๐ŸŽ‰"
Copied!

This script does it all:

  • Sets up your environment variables ๐Ÿ”
  • Installs necessary software ๐Ÿงฐ
  • Builds your app ๐Ÿ—๏ธ
  • Configures Nginx as a reverse proxy ๐Ÿšฆ
  • Sets up SSL for secure connections ๐Ÿ”’
  • Configures the firewall ๐Ÿงฑ

#5. ๐Ÿงฉ Putting It All Together

Hereโ€™s how to use this setup:

  1. In development:

    • Run your Express server: npm run dev (from the project root)
    • Run your React app: cd client && npm run dev
    • Your app is now running on two ports, but the proxy makes it feel like one!
  2. For production:

    • Push your code to your server
    • Run the deploy script: ./deploy.sh
    • Watch as your app magically comes to life on a single domain! โœจ

And there you have it! Youโ€™ve just learned how to set up and deploy a MERN stack application like a pro. ๐Ÿ†

Remember, deployment might seem tricky at first, but with practice, youโ€™ll be deploying apps in your sleep (though we donโ€™t recommend coding while sleeping ๐Ÿ˜ด).

Happy coding, and may your servers always be up and your bugs be few! ๐Ÿš€๐Ÿž