How to deploy a Node.js and PostgreSQL App on Render
The news that Heroku is discontinuing its free tier has sparked widespread concern in the developer community. Since then, many developers have been looking for free alternative hosting platforms to host and test their web applications. To address these concerns, Render was created to provide developers with the quickest way to host their web apps, databases, cron jobs, and workers using a free tier account.
This tutorial will teach you how to deploy a Node.js and PostgreSQL App on Render.
What is Render?
Render is a unified cloud platform that allows you to build and run all of your apps and websites while providing free TLS certificates, a global CDN, DDoS protection, private networks, and Git auto deploys. In addition, Render allows you to host static sites, backend APIs, databases, cron jobs, and other types of applications in a single location.
Prerequisites
To follow this tutorial, ensure you have the following installed on your computer.
- Node.js version 14 or later
- PostgreSQL database version 14 or later
Also, the code for this tutorial is available on GitHub. Feel free to clone and follow along.
Set up Express app
If you’re setting up your project from scratch, create a new folder for the project and enter it. Then create a package.json
file by running the following command in your terminal.
npm init -y
Next, create an src folder in the folder
and in the src folder, create a server.ts
file. We’ll use this file later to set up our Express server.
Set up TypeScript
Next, we’ll add TypeScript as a dev dependency. We’ll also install the Express and Node.js @types declaration packages, which provide type definitions in the form of declaration files. We’ll do that by running this command below:
npm i -D typescript @types/express @types/node
In the above command, we added the -D
flag to install the packages as devDependencies. Create a tsconfig
file in the project’s root directory and add the following configurations:
{
"compilerOptions": {
"lib": ["es5", "es6", "dom"],
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"rootDir": "./",
"outDir": "./dist"
}
}
Install dependencies
Now, let’s install the third-party dependencies required to run this application. First, we’ll install dotenv
, pg
, typeorm
and their types by running this command:
npm install dotenv pg typeorm
We’ll use typeorm
to connect to our database, create entities, and use the data it provides to perform CRUD operations. In the server.ts
file, create an Express server with the code snippets below:
import express, { Express } from 'express';
import * as dotenv from 'dotenv'
dotenv.config();
const app: Express = express();
const PORT = process.env.PORT;
app.use(express.json());
app.listen(PORT, ()=> console.log(`Server listening to port ${PORT}`));
In the above code snippet, we’ve set up an express server, used the dotenv package to load our environment variables, and set up middleware to parse the request payload.
Configure database
Let’s configure our application to connect to a PostgreSQL database using the typeorm package we installed. To start, create a config/dataSource.ts
file and add the code snippets below:
import { DataSource } from "typeorm";
export const myDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "<YOUR DB USERNAME>",
password: "<YOUR DB PASSWORD>",
database: "<YOUR DATABASE NAME>",
entities: [__dirname + '/../**/*.entity.js'],
synchronize: true,
});
In the above code snippet, we imported the typeorm DataSource
class and passed the database credentials. Replace <YOUR DB USERNAME>
, <YOUR DB PASSWORD>
, and <YOUR DATABASE NAME>
with your database username, password, and database name, respectively. We also specified the location for our entities and set synchronize to true to synchronize our database. Now, update the code in the server.ts
file to initialize the data source with the code snippets below:
//...
myDataSource
.initialize()
.then(() => {
console.log("Data Source initialized!")
})
.catch((err) => {
console.error(err)
})
//...
Create entity
With our database initialized, let’s create a task entity and define the structure of the entity class. To do that, create an entity/task.entity.ts
file src folder and add these code snippets:
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Task {
@PrimaryGeneratedColumn()
id: string;
@Column()
name: string;
@Column()
completed: boolean;
}
In the above code snippets, we imported the Entity
, which will map the Task
class to a database table, the Column
decorator to define our entity properties, and the @PrimaryGeneratedColumn()
to create a randomly generated id for each data in our table.
Create services
Now, let’s create some CRUD services to add and store some tasks in our database. To do that, make a service/taskService.ts
and add the following code snippets:
import { Task } from "../entity/task.entity";
import { myDataSource } from "../config/dataSource";
export const getAll = async (): Promise<Task[]> => {
return await myDataSource.getRepository(Task).find();
};
export const create = async (task: Task): Promise<Task> => {
return await myDataSource.getRepository(Task).save(task);
};
export const updateOne = async (id: string): Promise<any> => {
return await myDataSource
.getRepository(Task)
.update({ id }, { completed: true });
};
In the above snippet, we imported our task entity and the dataSource object we exported from our database. Then, we created a function to fetch all the tasks, get one task, and update a task using the getRepository
function, which takes our entity id as a parameter. The getRepository
function provides us with the methods to perform CRUD operations in our database.
Create controllers
Next, let’s create some controllers to handle the incoming requests on our application and return the required responses. Create a controllers/taskController.ts
file in the src folder and add the code snippets below:
import { Request, Response } from "express";
import { create, getAll, updateOne } from "../service/taskService";
export const createTask = async (req: Request, res: Response) => {
try {
const { name } = req.body;
const task = await create({ name, completed: false, id: null});
res.status(200).json(task);
} catch (e) {
res.status(500).json("an error occurred");
console.log(e);
}
};
export const getAllTask = async (req: Request, res: Response) => {
try {
const tasks = await getAll();
res.status(201).json(tasks);
} catch (e) {
res.status(500).json("an error occurred");
console.log(e);
}
};
export const updateTask = async (req: Request, res: Response) => {
try {
const tasks = await updateOne(req.params.id);
res.status(201).json(tasks);
} catch (e) {
res.status(500).json("an error occurred");
console.log(e);
}
};
In the above code snippets, we imported the service functions we created and created the controller functions for each service.
Create routes
Now, let’s create the route handlers for the controllers we have created. To do that, create a routes/routeController.ts
file in the src folder and add this code snippet:
import {
createTask,
getAllTask,
updateTask,
} from "../controllers/taskController";
import express, { Router } from "express";
const router: Router = express.Router();
router.route("/task").get(getAllTask).post(createTask).put(updateTask);
export default router;
Then update the server.ts file to use the routes we’ve defined with the code snippet below:
//...
app.use('/api', router);
//...
Deploy on Render
At this point, our application is set. Let’s proceed to deploy it on Render. Sign up for free on Render with your GitHub, GitLab, or Gmail account to get started.
Once you’ve signed up and confirmed your email, you’ll be redirected to your Render dashboard.
Then click on the New + button to select the service you want to host.
Host the database
We’ll start by hosting our Postgres database, so select PostgreSQL from the dropdown list.
Next, enter the details for the database. Enter the name and leave the type as free tier. Choose at least version 14.
Then press the Create Database button and save the credentials in a safe place.
Host the app
With the database hosted, let’s proceed to hosting the application. Before that, update the code in the config/dataSource.ts
file to use the credentials from Render:
import { DataSource } from "typeorm";
export const myDataSource = new DataSource({
type: "postgres",
host: process.env.HOSTNAME,
port: 5432,
username: process.env.USERNAME,
password: process.env.PASSWORD,
database: process.env.DATABASE_NAME,
entities: [__dirname + '/../**/*.entity.js'],
synchronize: true,
});
Then update the package.json
to have the scripts below for use on the Render platform:
...
"scripts": {
"build": "tsc",
"start": "node ./dist/src/server.js"
},
...
Push the code to GitHub. Now click on the Web Service tab from your Render dashboard:
Next, select the project’s GitHub repository and enter the following details:
- Name:
express-app-demo
- Build Command: npm i;
npm run build
- Start Command:
npm run start
- Plan Type:
Free
Then scroll down and click on the Advanced button, click on the Add Environment Variable and add the following database credentials from your Render database for:
HOSTNAME= <HOSTNAME>
USERNAME= <USERNAME>
PASSWORD= <PASSWORD>
DATABASE_NAME= <DATABASE_NAME>
PORT = 5432
Finally, click the Create Service button and wait for the application deployment to complete.
Once the deployment is finished, the application status will show Deploy succeeded
.
Now, you can go ahead and test out the API with this URL: https://express-app-demo.onrender.com/api/task
.
Please note that the URL used in this tutorial may be different from the one you will use in your own implementation. It’s recommended to test the API using your own URL to ensure proper setup and configuration.
Test the API
Finally, to demonstrate how to interact with a hosted API using cURL, let’s create and retrieve todos.
Use the following command to send a POST request to create a todo:
curl -X POST -H "Content-Type: application/json" -d '{"name":"my todo"}' <Your Render API URL>
After sending a POST request to create a todo, you can use the following command to send a GET request to retrieve the todo that was created:
curl -X GET <Your Render API URL>
Conclusion
This tutorial taught us how to deploy a Node.js and PostgreSQL App on Render. First, we started by introducing Render. Then, as a demonstration, we built a Node.js RESTful web application to manage a task and deployed it on Render.
Render is an exciting tool, and I recommend checking out the documentation to learn more. I hope you enjoyed this article and happy coding!
If you like articles like these, browse the Mattermost Library and continue your learning.