Deploy Fullstack App and Create CI/CD
Important Links
INTRO
In this article, I’m going to show you how to deploy a full-stack application from start to finish, including how to set up a CI/CD pipeline to make deployments easier. By the end of this article, you’ll have your app live and ready to use, and future updates will be automatically deployed! S0, Let’s get started.
STEP 1: Clean Up Console Logs
Before we deploy, let’s start by cleaning up our code. The first step is to remove all console.log
statements from both the frontend and backend directories. This is crucial for keeping your production code clean and optimized.
Go through your files, and look for console.log
statements in both the client and server code. Remove them one by one. You can also use a text editor’s search function (cmd + shift + f) on mac to find all instances of console.log
and delete them.
STEP 2: Move some Backend Files to Root
Next, we need to bring four important files from the backend into the root
directory. These are:
- `package.json`
- `package-lock.json`
- `.gitignore`
- `.env`
When deploying a full-stack application, we want to simplify the structure so that deployment services (like Render) can easily detect both the frontend and backend in one project. Having these files in the root ensures that everything is set up in one place and makes deployment smoother.
STEP 3: Modify server.js to Serve Frontend Files
Now, let’s modify our server.js
file to handle the frontend build files.
We’re adding the following lines to server.js
to tell the server to serve the static files of the frontend from the dist
folder.
const path = require("path");
// Paste below code before the routes
app.use(express.static(path.join(__dirname, "../client/dist")));
app.get("*", (_, res) => {
res.sendFile(path.resolve(__dirname, "../client/dist/index.html"));
});
This code ensures our Express server serves the frontend build files from the dist
folder.
In a full-stack application, the backend needs to serve the frontend when deployed. Vite, which we use for the frontend, compiles everything into a dist
folder. We’re telling the server to serve that dist
folder so users can see the React frontend.
app.use(express.static(path.join(__dirname, "../client/dist")))
: This tells the server to treat the dist
folder as static, so it can serve the compiled HTML, CSS, and JS files.
app.get("*", (_, res) => { res.sendFile(path.resolve(__dirname, "../client/dist/index.html")); })
: This ensures that for any route in the frontend (e.g., /about
, /contact
), the server serves index.html
. This is essential for single-page applications (SPAs).
STEP 4: Update package.json Scripts
The next step is to update the scripts in the root package.json
file. Add the following lines:
"start": "node server/server.js",
"dev": "nodemon server/server.js",
"build": "npm install && npm install --prefix client && npm run build --prefix client",
These scripts will allow us to start, develop, and build our application efficiently.
"start"
: This script starts the server using Node.js, which is used when the app is deployed."dev"
: This script usesnodemon
, a tool that restarts the server automatically when you make changes during development."build"
: This script installs dependencies for both the backend and frontend, and builds the frontend using Vite.
STEP 5: Build the Frontend
Let’s now build the frontend using Vite. Run the following command inside the frontend directory. So navigate to the client folder and Run the command:
cd client
npm run build
When using Vite for the frontend, the build
command generates optimized production-ready files in a dist
folder. These files are what will be served by the backend. The dist
folder contains everything your app needs for production.
Create a GitHub Repository
Now, we need to upload our code to GitHub. If you don’t have a repository yet, create one and push your code. This will make it easy to integrate with Render for deployment.
GitHub serves as our version control system and makes it easier to collaborate. Also, Render will pull the code directly from GitHub when deploying the application.
Create a new repository on GitHub and follow the instructions to push your code there. Make sure to include the .gitignore
file so that unnecessary files aren’t uploaded.
- Go to GitHub and log in.
- In the top-right corner, click the + icon and select New repository.
- Give your repository a name (e.g.,
my-project
). - Choose the repository as Public or Private based on your preference.
- Click on Create repository.
Initialize a Local Git Repository
Open your terminal, navigate to the project folder, and initialize the local repository:
cd /path/to/your/project
git init
Add Files to the Repository: Add your project files to the Git staging area:
git add .
Select the Branch:
git branch -M main
git push -u origin main
Commit Your Changes: Commit the files you’ve added:
git commit -m "Initial commit"
Connect Your Local Repository to GitHub: Copy the HTTPS URL of the repository from GitHub. Then, link your local repository to the GitHub repository:
git remote add origin https://github.com/your-username/my-project.git
Push Your Code to GitHub:
git push -u origin main
Note: If your default branch is master
, use master
instead of main
.
Your code is now pushed to GitHub! You can visit the repository on GitHub to verify that everything is uploaded.
STEP 7: Deploy on Render
Head over to Render.com, create an account if you don’t have one, and link your GitHub repository. Render will automatically detect your project settings. Select the appropriate branch, and we’re good to go. Add script in build and start section.
Build: npm run build
Start: npm run start
Go to Render, create an account, and choose “Create New” > “Web Service.” Connect your GitHub account, select your repository, and follow the prompts.
STEP 8: Add Environment Variables on Render
Once your project is linked, navigate to the “Environment” section in Render’s dashboard and add all necessary environment variables, such as our database credentials, API keys, etc. Like add all of the variables that we have in the env file.
On the Render dashboard, go to the environment settings and add your variables. These should match the ones you used in your .env
file locally.
Now for the client url, for now, let’s leave it to localhost:5173. We will change this env variable, because it will not be the local host but the URL render will give us for our service. But for now, leave it to localhost
STEP 9: Deploy Your Project
Now, hit “Deploy” and wait for the build process to complete.
Render will now build and deploy both the backend and frontend. It may take a few minutes, but once it’s done, your project will be live.
You’ll see a live URL once the deployment is successful.
STEP 10: Update Frontend URLs
Once the project is deployed, we will get the application URL right. So now we, make sure to update any hardcoded localhost
URLs in the frontend directory with the live URL that Render provided us.
We’re replacing any hardcoded localhost
URLs in the frontend with the live URL from Render.
Search through your frontend code for localhost:3000
and replace it with the Render-provided URL.
Also, make sure to change Client URL in environment setting of render.
STEP 11: Test the Application
Now that everything is deployed, it’s time to test the application. Make sure everything works as expected, both on the frontend and backend.
It’s important to ensure that all features, API calls, and routes work as expected after deployment.
Visit the live URL and navigate through your app and test the functionality and check console for any error.
STEP 12: Setup CI/CD Pipeline
Let’s set up a CI/CD pipeline so that future changes are automatically deployed. Add the following configuration to your .github/workflows/ci-cd.yaml
file.
name: CI/CD Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies (client)
run: |
cd client
npm install
- name: Install dependencies (server)
run: |
cd server
npm install
- name: Build client
run: |
cd client
npm run build
- name: Deploy to Render
env:
RENDER_API_KEY: ${{ secrets.RENDER_DEPLOY_HOOK }}
run: |
curl -X POST \
-H "Authorization: Bearer $RENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"serviceId": "srv-YOUR_SERVICE_ID"}' \
https://api.render.com/v1/services/srv-YOUR_SERVICE_ID/deploys
CI/CD (Continuous Integration and Continuous Deployment) pipelines help automate the deployment process. This way, every change pushed to GitHub will automatically be built and deployed, saving you time and reducing errors.
This ensures that every push to the main
branch will trigger a new deployment.
The serviceId
here is specific to your Render service, and you can find it in your Render dashboard[in you project’s service URL]. You’ll need to set up the RENDER_DEPLOY_HOOK
in GitHub Secrets to keep your API key secure too.
STEP 13: Get Render Deploy Hook and Service ID
Finally, we need to get the deploy hook and service ID from the Render dashboard. This allows our CI/CD pipeline to trigger automatic deployments.
- The deploy hook can be found in the settings of your Render’s project service.
- Navigate to the settings then scroll down until you find Deploy Hook Section, then copy the render hook and paste it into our CI/CD Pipeline.
- And as for the service ID it is present in the URL of your project dashboard. So at the top of the window, you can see this
srv-crgXXX
or something like that, So that’s the service id.
So that’s pretty much all for this article. I hope it helped you. So I’ll see you in the next one. Till then bye bye.