The MERN stack—MongoDB, Express.js, React, and Node.js—has revolutionized web development. It offers a full-stack JavaScript solution that's both powerful and flexible. Developers love it for its efficiency in building dynamic web applications. But with great power comes great complexity.
Setting up a MERN project can be a headache. Dependency conflicts. Version mismatches. Hours lost to configuration. It's enough to make even seasoned developers pull their hair out.
Enter Daytona.
Daytona is simplifying Development Environment Management (DEM). It's not just a tool; it's a lifeline for developers drowning in setup woes. Imagine a world where your workspace is consistent, repeatable, and easily deployable across any platform. That's the world Daytona creates.
By the end of the guide, you'll understand why more developers are turning to Daytona. It's not just about making setup easier—it's about freeing you to do what you do best: create amazing web applications.
Whether you're working on your local machine or a remote server, this guide will provide you with the steps to quickly get up and running with the MERN stack.
Overview of MERN Stack
MERN stack is a powerful combination of four technologies:
MongoDB: A NoSQL database that stores data in flexible, JSON-like documents.
Express.js: A minimal web application framework for Node.js, enabling robust API creation.
React.js: A JavaScript library for building user interfaces, particularly for single-page applications where data changes over time.
Node.js: A JavaScript runtime for building fast, scalable network applications.
Each component of the MERN stack plays a crucial role in the development process, providing a seamless integration that allows for the creation of dynamic and interactive web applications.
For a complete codebase, including all components, configurations, and setup files discussed in this guide, check out our GitHub repository: https://github.com/daytonaio-experiments/starter-mern-saas
Setting Up the Development Environment
Before we dive into setting up your MERN stack environment, let's make sure you have the necessary tools and a basic understanding to follow along smoothly. Here are the prerequisites you'll need:
Prerequisites
Basic understanding of JavaScript.
IDE (Integrated Development Environment) such as Visual Studio Code (VSC).
Docker installed as a provider for spinning up the Daytona workspace inside the container.
Getting Started with the MERN App
We’ve prepared a comprehensive example repository to facilitate your initiation into the MERN stack. We're using the Creator Relationship Management (CRM) App, a full-stack web application designed for influencers, podcasters, and creators. It efficiently manages and showcases client relationships with features like client data management, responsive design, search capabilities, interaction tracking, and project management.
App utilizes MongoDB for data management, Node.js and Express.js for the server-side framework, and React.js with Tailwind CSS for a dynamic user interface.
Setting up MongoDB Cluster
To set up MongoDB, refer to the MongoDB Atlas Getting Started guide. This guide will walk you through creating a MongoDB cluster, getting the Mongo URI, and adding your IP address to the network access settings. Once you have your Mongo URI, you can integrate it into your project configuration.
NOTE: If you encounter problems connecting to the database, go to your MongoDB cluster's "Network Access" settings, click "Add IP Address," and select "Allow Access from Anywhere."
Setting up Daytona for Development Workspace
Setting up a local development environment can be painful due to version mismatches and configuration issues. Daytona automatically sets up a Node.js environment with necessary tools, manages port forwarding seamlessly, and runs essential post-create commands, ensuring a consistent, ready-to-use setup that minimizes manual configuration.
To set up Daytona, refer to the Daytona installation documentation. This guide provides detailed steps to get Daytona up and running efficiently, ensuring your development environment is well-configured.
Setting Up Your MERN Stack Environment:
Step 1: Create a Workspace
Initiate the process by executing the following command using this Git URL for the repository:
1daytona create https://github.com/daytonaio-experiments/starter-mern-saas.git
Step 2: Set a Preferred IDE
To set up your preferred IDE, list all supported IDEs using the following command and select from them.
1daytona ide
Step 3: Open the Workspace
Once you've selected your preferred IDE, open the Workspace by executing the following command. Select the workspace created above for this project, and it will open in your chosen IDE.
1daytona code
After following these commands, the repository will open in your preferred IDE.
Step 4: Starting the Backend Server
Create a .env file in the backend directory and add the following line inside it. Change the string below to match your MongoDB connection URI.
1MONGO_URI="mongodb+srv://<username>:<password>@cluster0.ce6av93.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0"
Start the backend server by navigating to the backend directory and running:
1npm run dev
Step 5: Starting the Frontend
Create a .env file in the frontend directory and add the following line:
1VITE_BACKEND_URL="http://localhost:8000/api/customers/"
Start the frontend server by navigating to the frontend directory and running:
1npm run dev
Your MERN app should now be up and running.
“assets/mern-app-ui.png” could not be found.
Structure of MERN Repository
The repository is organized into several key directories and files to facilitate efficient development:
Backend: This directory includes all server-side code, primarily using Node.js and Express.js.
Frontend: Contains the client-side application built with React.js and styled with Tailwind CSS.
.devcontainer: Configuration files for setting up a consistent development environment with Daytona, including the devcontainer.json file.
Effortless Setup: Daytona and .devcontainer in Action
Daytona provides a powerful development workspace that simplifies the setup and management of development environments, whether locally or remotely.
By automating the setup and maintenance of development environments, Daytona enables developers to focus on core tasks, reducing time spent on non-productive activities and increasing overall efficiency.
Daytona enhances developer productivity by offering a standardized and scalable platform, allowing developers to easily set up and access their development environments from anywhere, eliminating the need for local machine configurations.
Daytona streamlines the setup process of our MERN stack application, ensuring a consistent and efficient development experience across different machines.
Using .devcontainer for Development
devcontainer.json configuration provides a predefined development environment using Docker containers. Here’s the configuration used in this project:
1{2 "name": "Node.js, Express, React, MongoDB & Tailwind",3 "image": "ubuntu:22.04",4 "features": {5 "ghcr.io/devcontainers/features/common-utils:2.4.7": {6 "username": "daytona",7 "userUid": 1000,8 "userGid": 1000,9 "configureZshAsDefaultShell": true10 },11 "ghcr.io/devcontainers/features/node:1": {12 "nodeGypDependencies": true,13 "version": "lts",14 "nvmVersion": "0.40.0"15 },16 "ghcr.io/devcontainers/features/git:1": {}17 },18 "overrideFeatureInstallOrder": [19 "ghcr.io/devcontainers/features/common-utils",20 "ghcr.io/devcontainers/features/git",21 "ghcr.io/devcontainers/features/node"22 ],23 "portsAttributes": {24 "5174": {25 "label": "Frontend",26 "onAutoForward": "notify"27 },28 "8000": {29 "label": "Backend",30 "onAutoForward": "ignore"31 },32 "27017": {33 "label": "MongoDB",34 "onAutoForward": "ignore"35 }36 },37 "customizations": {38 "vscode": {39 "extensions": [40 "mongodb.mongodb-vscode",41 "dbaeumer.vscode-eslint",42 "esbenp.prettier-vscode",43 "bradlc.vscode-tailwindcss",44 "davidanson.vscode-markdownlint"45 ]46 }47 },48 "workspaceFolder": "/workspaces/starter-mern-saas",49 "onCreateCommand": "npm install -g nodemon",50 "postCreateCommand": "cd backend && npm install && cd ../frontend && npm install",51 "remoteUser": "daytona"52}
This configuration includes:
name: Specifies the name of the development environment.
image: Uses the
ubuntu:22.04
Docker image as the base for the development environment.features: common-utils: Adds common utilities (e.g., Zsh) with configurations for the user "daytona" (UID: 1000, GID: 1000) and sets Zsh as the default shell.
features: node: Installs the LTS version of Node.js with dependencies for
node-gyp
and manages Node versions using NVM 0.40.0.features: git: Installs Git to manage source code versioning.
overrideFeatureInstallOrder: Specifies the order of feature installation to ensure common utilities, Git, and Node.js are set up in the correct sequence.
portsAttributes: Sets up port forwarding with labels for the frontend (5174), backend (8000), and MongoDB (27017) services.
customizations: Installs essential Visual Studio Code extensions, including support for MongoDB, ESLint, Prettier, Tailwind CSS, and Markdown linting.
workspaceFolder: Sets the workspace folder to
/workspaces/starter-mern-saas
.onCreateCommand: Installs
nodemon
globally usingnpm install -g nodemon
.postCreateCommand: Installs dependencies in both the backend and frontend directories using
npm install
.remoteUser: Sets "daytona" as the remote user for running commands within the container.
Daytona’s integration with .devcontainer allows us to create a consistent development environment that is easily reproducible.
This is particularly beneficial for our MERN stack application, as it ensures all team members work in the same environment, avoiding issues related to local machine configurations. Seamless setup of ports for the front end and backend enhances the development experience, allowing for quick previews and efficient debugging.
Breaking Down the MERN Stack: How Everything Comes Together
Now that we've successfully set up and run our MERN stack application, it's time to delve deeper into its structure. Let's break down the different components of our application to see how they work together seamlessly.
Overview of the Backend Structure
Let’s dive into the backend structure to see how everything fits together. Our backend is designed to keep things organized and maintainable. Here’s how we’ve set it up:
Models: This directory contains Mongoose models, which define the structure of your MongoDB documents, such as customer.js.
Routes: This directory houses the route handlers that map HTTP requests to controller functions, like customerRoutes.js.
Services: Business logic is separated into service files, promoting modular and maintainable code, such as customerService.js.
Main Files:
app.js: Initializes the Express app and configures middleware.
database.js: Sets up and manages the MongoDB connection.
index.js: Entry point that starts the server.
Server Setup, Routes, and API Endpoints
Server Setup: In app.js, the Express application is created, and middleware like body-parser and CORS are configured.
Routes: Defined in customerRoutes.js, routes are linked to controller functions for handling requests.
API Endpoints: Examples include /customers for fetching all customers and /customers/:id for fetching, updating, or deleting a specific customer.
Overview of the Frontend Structure
Our frontend is built for clarity and scalability, making it easy to navigate and expand as needed. Here’s a look at how it’s organized:
public/: Contains static files used in application.
src/: Holds the React components and other frontend logic.
src/assets/: Stores static assets such as images.
src/components/: Contains all React components
src/App.css: Main CSS file for the app.
src/App.jsx: Root component that sets up the app structure.
src/index.css: Global CSS file.
src/state.js: Manages application state.
Main Components and State Management
Main Components: App.jsx (main application component), main.jsx (main entry point rendering the app), and other specific components under components/.
State Management: Using React's useState and useEffect hooks for state management, e.g., fetching and displaying data in App.jsx.
Overview of MongoDB Database
Database is the backbone of our application, and setting it up correctly is crucial. Here’s how we’ve configured it:
Configuration: MongoDB connection settings in database.js and the .env file.
Connection Setup: Using Mongoose, the database.js file connects to MongoDB. Ensure the MongoDB URI is configured in your environment variables.
Required Packages: Mongoose for object data modeling.
Customizing the MERN Application
Customizing your application allows you to tailor the functionality and user experience to better meet your specific requirements. By adding new routes and components or modifying existing ones, you'll gain hands-on experience with both the backend and frontend aspects of the MERN stack.
Here are some example customizations that you can follow to get a hands-on understanding of how MERN applications work.
Adding New Routes and Modifying Existing
To customize the backend logic of your MERN application, you can add new routes or modify existing ones as per your requirements.
Adding New Routes
Define the Route: Create a new route file in the routes directory or add a new route handler in an existing file. For example, to add a new product route:
productRoutes.js
1const express = require('express');2const router = express.Router();3const productService = require('../service/productService');45router.get('/products', async (req, res) => {6 try {7 const products = await productService.getAllProducts();8 res.status(200).json(products);9 } catch (error) {10 res.status(500).json({ message: error.message });11 }12});1314module.exports = router;
Create Controller Functions: Implement the logic for the new routes in a service file.
productServices.js
1class ProductService {2 async getAllProducts() {3 // Logic to fetch all products4 }5}67module.exports = ProductService;
Update App Configuration: Register the new route in app.js.
app.js
1const productRoutes = require('./routes/productRoutes');23app.use('/api', productRoutes);
Modifying Existing Routes
Update Route Handlers: Modify the logic in existing route files or controller functions as needed.
Test Changes: Ensure new functionality works as expected by testing the updated endpoints.
Adding New React Components and Modifying Existing Ones
To enhance the frontend of your MERN application, you can add new components or modify existing ones:
Adding New Components
Create Component File: In the src/components directory, create a new component file, for example, ProductList.jsx.
ProductList.jsx
1import React, { useEffect, useState } from 'react';2import axios from 'axios';34const ProductList = () => {5 const [products, setProducts] = useState([]);67 useEffect(() => {8 const fetchProducts = async () => {9 const response = await axios.get('/api/products');10 setProducts(response.data);11 };12 fetchProducts();13 }, []);1415 return (16 <div>17 <h1>Products</h1>18 <ul>19 {products.map(product => (20 <li key={product.id}>{product.name}</li>21 ))}22 </ul>23 </div>24 );25};2627export default ProductList;
Integrate Component: Import and use the new component in App.jsx.
App.jsx
1import React from 'react';2import ProductList from './components/ProductList';34function App() {5 return (6 <div className="App">7 <ProductList />8 </div>9 );10}1112export default App;
Modifying Existing Components
Edit Component Logic: Open the component file you wish to modify and update the JSX or state logic as needed.
Test Changes: Ensure the updated component works correctly by running the application and verifying the changes.
Conclusion
Congratulations! You have successfully set up your MERN stack application, cloned the example repository, and customized both the backend and frontend.
With Daytona, you’ve ensured a consistent and efficient development environment, significantly simplifying the setup process and enhancing productivity. By leveraging Daytona's capabilities, you've streamlined the complexities of local setup, ensuring a seamless and uniform workspace for all developers involved.
Now, you can focus on developing and enhancing your application without worrying about environmental inconsistencies or setup issues. If you encounter any challenges or have further questions, the Daytona team is readily available to assist you on Slack, or you can open an issue on the Daytona GitHub repository. Happy coding, and enjoy your development journey with the MERN stack!