How to setup and deploy a fullStack(mern) application on Vercel and Render
Setting up a full-stack project and deploying that can be tricky. Let me show you how to do that.
Video tutorial
I have already created a video about it on my youtube channel. Check that out for more details.
If you like this video, please like share, and Subscribe to my channel.
Setup server
We are going to build a mono repo. This means the server and the client-side code will live inside a single repository.
1. Create directories
1mkdir mern-app2cd mern-app3mkdir client server4cd server
2. Initialize package.json and install dependencies
1npm init -y2npm install cors dotenv express mongoose # dependencies3npm install @babel/cli @babel/core @babel/node @babel/preset-env babel-plugin-module-resolver concurrently nodemon --save-dev #dev dependencies45npm install cors dotenv express mongoose && npm install @babel/cli @babel/core @babel/node @babel/preset-env babel-plugin-module-resolver concurrently nodemon --save-dev
Or use a single command:
1npm init -y && npm install cors dotenv express mongoose && npm install @babel/cli @babel/core @babel/node @babel/preset-env babel-plugin-module-resolver concurrently nodemon --save-dev
Package Explanation:
- Cors: To handle cors error
- Express: Nodejs framework
- Dotenv: For handling environment variables
- Mongoose: An ODM for MongoDB. In simple words, It is an abstraction over vanilla MongoDB SDK. It simplifies the way you interact with MongoDB and gives you much more features.
- Concurrently: It allows you to run multiple dev servers within a single terminal.
- Nodemon: To restart the dev server automatically when we will change our files.
- Babel: To compile our javascript code
3. Setup Babel for absolute import(optional)
Absolute Import vs Relative Import
- Create a
.babelrc
file.
1{2 "presets": ["@babel/preset-env"],3 "plugins": [4 [5 "module-resolver",6 {7 "root": ["./src"]8 }9 ]10 ]11}
With this, we are saying that we want src
as our root and we always import files relative to the src
directory.
- Create a
jsconfig.json
file inorder to have intelisense.
1{2 "compilerOptions": {3 "baseUrl": "./src"4 }5}
4. Create scripts
1{2 "scripts": {3 "dev": "nodemon --exec babel-node src/index.js",4 "build": "babel src -d dist",5 "start": "node dist/index.js",6 "both-dev": "concurrently \"npm run dev\" \"npm --prefix ../client/ run dev\""7 }8}
- dev: To run the dev server with nodemon and also we always want to compile the code with babel.
- build: Compile the code with babel for production.
- start: Start the node server with the compiled code.
- both-dev: To run the both client and backend dev server in a single command.
6. Create a basic node-server
We are going to build a simple to-do application that you don't have to understand how it works.
- First, let's create a
Todo
Model. With the model, we can interact with MongoDB collections.
1import { Schema, model } from 'mongoose'23const todoSchema = new Schema({4 title: String,5})67const todoModel = model('todo', todoSchema)8export default todoModel
- Create the basic server. We have just created some simple API for CRUD applications.
1import { config } from 'dotenv'2import express from 'express'3import { connect as mongoConnect } from 'mongoose'4import cors from 'cors'56import Todo from 'models/Todo'78config()910mongoConnect(process.env.MONGO_URI)11 .then(() => console.log('db connected'))12 .catch(err => console.log(err))1314const app = express()1516// To parse the request body17app.use(express.urlencoded({ extended: true }))18app.use(express.json())1920// To handle cors error21app.use(cors())2223app.get('/hello', (_, res) => res.send('Hello from Cules Coding'))2425app.post('/addTodo', async (req, res) => {26 const { body } = req2728 const newTodo = new Todo(body)29 const savedtodo = await newTodo.save()3031 return res.send(savedtodo)32})3334app.delete('/deleteTodo', async (req, res) => {35 const {36 body: { todoId },37 } = req3839 const response = await Todo.findByIdAndDelete(todoId)40 return res.send(response)41})4243app.get('/getAllTodos', async (_, res) => {44 const response = await Todo.find({})45 return res.send(response)46})4748const port = process.env.PORT || 80004950app.listen(port, () => console.log(`Server is running on ${port}`))
Now, our server is ready.
Let's build our client application
1. Create nextjs application
You don't have to use nextjs. You can also use create-react-app.
Just go to the client
directory and run the command:
1npx create-next-app@latest . --use-npm
You can also optionally install chakra-UI which is an awesome React UI framework. You can learn about Chakra-UI from my crash course.
I am going to use it so that the UI doesn't look terrible. Also, install Axios to make requests to our backend server.
1npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6 axios
2. Add env variables
1NEXT_PUBLIC_API_URL=<backend_api_url>
3. Add the code for todo
1// pages/_app.jsx23// This is only needed if you are using chakra-UI45import { ChakraProvider } from '@chakra-ui/react'67function MyApp({ Component, pageProps }) {8 return (9 <ChakraProvider>10 <Component {...pageProps} />11 </ChakraProvider>12 )13}1415export default MyApp
1// pages/index.jsx23import {4 Heading,5 Center,6 Button,7 Box,8 Input,9 FormControl as Form,10} from '@chakra-ui/react'11import axios from 'axios'12import { useEffect, useState } from 'react'1314const axiosInstance = axios.create({15 baseURL: process.env.NEXT_PUBLIC_API_URL,16})1718const Home = () => {19 const [todos, setTodos] = useState([])20 const [inputVal, setInputVal] = useState('')21 const [refresh, setRefresh] = useState(false)2223 const getTodos = () => {24 axiosInstance.get('/getAllTodos').then(res => setTodos(res.data))25 }2627 useEffect(() => {28 getTodos()29 }, [])3031 useEffect(() => {32 if (refresh) {33 getTodos()3435 setTimeout(() => {36 setRefresh(false)37 })38 }39 }, [refresh])4041 const deleteTodo = todoId => () => {42 axiosInstance43 .delete('/deleteTodo', {44 data: { todoId },45 })46 .then(() => setRefresh(true))47 }4849 const addTodo = e => {50 e.preventDefault()51 axiosInstance.post('/addTodo', { title: inputVal }).then(() => {52 setRefresh(true)53 setInputVal('')54 })55 }5657 return (58 <>59 <Heading mb={12}>MERN</Heading>6061 <Form mb={10} as='form' onSubmit={addTodo}>62 <Input63 onChange={e => setInputVal(e.target.value)}64 width='300px'65 placeholder='New Todo'66 size='md'67 value={inputVal}68 />69 <Button type='submit'>Add</Button>70 </Form>7172 {todos.map(({ _id, title }) => (73 <Box key={_id} mb={10}>74 <Center w='180px' h='80px' bg='red.200'>75 {title}76 </Center>77 <Button onClick={deleteTodo(_id)}>Delete</Button>78 </Box>79 ))}80 </>81 )82}8384export default Home
The above code will just get all the todos and render them to the UI. You can also add a new todo.
So our full-stack app is now done. Now we can deploy our application. First, we will deploy our backend to Render
I would highly recommend watching the deployment part of the video to understand things clearly.
Deploy on Render
- Go and sign up at https://render.com/
- Go to the dashboard and create a new web service
- If you haven't connected your account with GitHub, please do so. And give access to Render to your repo.
- Select the repo that you want to deploy and add the following information(required):
- Name: Whatever you want
- root directory: ./server
- environment: Node
- build command: npm install && npm run build
- start command: npm start
- Add an environment variable from the advanced settings
Now you will be able to deploy the application.
If your deployment is successful then you should have a log like this:
1Dec 14 09:35:19 AM > server@1.0.0 start /opt/render/project/src/server2Dec 14 09:35:19 AM > node dist/index.js3Dec 14 09:35:19 AM4Dec 14 09:35:26 AM Server is running on 100005Dec 14 09:35:29 AM DB connected
Deploy on vercel
- Go and sign up at https://vercel.com/
- Go to the dashboard and create a project
- If you haven't connected your account with GitHub, please do so. And give access to vercel to your repo.
- Select the repo that you want to deploy and add the following information(required):
- Name: Whatever you want
- framework preset: Nextjs(or Whatever you are using)
- root directory: select client from the options
- Add the necessary environment variables like NEXT_PUBLIC_API_URL
Now you will be able to deploy the application.
Shameless Plug
I have made an Xbox landing page clone with React and Styled components. I hope you will enjoy it. Please consider like this video and subscribe to my channel.
That's it for this blog. I have tried to explain things simply. If you get stuck, you can ask me questions.
Contacts
- Email: thatanjan@gmail.com
- LinkedIn: @thatanjan
- Portfolio: anjan
- Github: @thatanjan
- Instagram : @thatanjan
- Twitter: @thatanjan
Blogs you might want to read:
- Eslint, prettier setup with TypeScript and react
- What is Client-Side Rendering?
- What is Server Side Rendering?
- Everything you need to know about tree data structure
- 13 reasons why you should use Nextjs
- Beginners guide to quantum computers
Videos might you might want to watch: