Get Started with the PERN Stack: An Introduction and Implementation Guide
Choosing a web development stack depends on your final product’s desired features and capabilities as well as your development team’s qualifications. The PERN Stack is one of the most popular stacks out there — and this article will introduce you to its basics and how you can use it to build an application.
What is the PERN Stack
Alongside MERN, LAMP, MEAN, and MEVN, the PERN Stack is one of many useful sets of technologies to build web or mobile applications. As the acronym suggests, it is comprised of four key components: PostgreSQL, Express, React, and Node.js. When the components are combined, you can build a full-stack application with CRUD operations.
Let’s go through each component and understand a little bit more about them:
- PostgreSQL is an open-source object-relational database management system (ORDBMS) that supports both SQL (relational) and JSON (non-relational) querying. It’s ACID-compliant and table-based, with complete constraints, triggers, and roles. These features are what allow for the creation of relationships between data. This, in turn, leads to benefits such as extensibility, scalability, reliability, performance, and robustness.
- Express is a popular and free JavaScript web framework designed for use with Node.js. Its purpose is to build web applications and, especially, APIs, quickly and easily. It provides a set of features for the routing, middleware and handling of HTTP requests and responses. In the PERN Stack, this component handles the application back-end.
- React is a JavaScript library for building user interfaces. In this stack, this component is responsible for the front-end.
- Node.js is a JavaScript runtime for developing server-side and networking applications, such as web servers, easily.
Here’s an illustration that shows the flow of data in the PERN Stack:
How to implement the PERN Stack
Now that everything is explained and you understand this stack better, let’s move on to the practical part: A step-by-step example of how to implement the PERN Stack.
Prerequisites:
First, make sure you have the right tools. The following is a list of software you need to complete this implementation successfully:
- Container machine, e.g., Docker
- Node.js
- Code editor, e.g., VSCode
- npx
- (Optional) pgAdmin
- (Optional) Postman or Insomnia
- (Optional) nodemon
If you already have all this, you’re set to begin! In the following section, you have a guide with all the necessary and essential steps to implement PERN Stack. So, let’s get on this journey!
Step 1: Project Structure
Build a new folder for the project
Let’s start this by creating a new folder for the project.
This main folder should have two folders in it: /server
and /client
, which, as their name tells, one will have the files related to the server (API) and the other one will have the files related to the client (UI).
This will help keep your project clean and organized.
Initialize the project
Server: Initialize the project using npm init
in the server folder to create a Node.js project. Next, install the required dependencies for this basic PERN Stack example using npm
:
- express: to use the Express web framework in the project;
- pg: to use PostgreSQL in the project;
- dotenv: to securely store and manage environment variables in the project, while separating them from code.
Client: Initialize the project using npx create-react-app
on the client folder. After that, we need to install the required dependencies, this time for the front-end:
- axios: to enable making HTTP requests from the front-end.
When all of this is done, you’ll have something like this:
Step 2: Database
Now that we have the project base, we need a database to store our data, so let’s use PostgreSQL.
Set up PostgreSQL
To simplify this example, we need a PostgreSQL container instead of configuring it manually. To do so, we need to use a Docker PostgreSQL image:
docker run --name <container_name> -e POSTGRES_USER=<postgres_user> -e POSTGRES_PASSWORD=<postgres_password> -p 5432:5432 -d postgres
Create the database and populate it
Create the database and populate it with the desired tables, data, and relationships.
This can be done in an easy way:
- Create a SQL script that runs and populates the database. To do this, I used the script below to create a table with a few random items (as an example, I used some products):
After that, copy the SQL script to the inside of the container and execute it:
docker cp ./<path_to_file>/init.sql <container_name>:/tmp/init.sql
docker exec -ti <container_name> /bin/bash -c "psql -U <postgres_user> -d <postgres_database> -f /tmp/init.sql"
Alternatively, if you want, you can do it by:
- Establishing a bash session with
docker exec -ti <container_id> bash
and then usepsql
; - Using pgAdmin.
Step 3: Building the API
All great! Time to build our API!
The API starting point is index.js
, the file you need to create in the server folder. This file will define all the routes and import express. Additionally, it also has the necessary configurations set so that Node.js server runs on the desired port and starts listening for incoming requests.
Create the API
Change the file index.js
to create the API.
Run the API
Now, if you run node index.js
or npx nodemon index.js
on our terminal, the following message will show up on your console:
Server listening on the port 9000
Test the API
To test if the API is working use Postman to make the request. In this case, a simple GET request to http://localhost:9000/ should return the string “Hello World!”.
Note that this is just the basis of PERN Stack. Normally, a web application requires numerous requests that can make projects a little bit confusing. To make bigger projects more organized, and less confusing, there are some optional files that can help us fix this problem, such as:
- Routes: Forward the requests to the appropriate controller functions, to handle them;
- Controllers: Contain functions to handle specific routes and determine the appropriate response to send back to the user;
- Services: Contain functions to handle possible errors;
- Database files: Contain functions to perform CRUD operations in a database.
Step 4: Configure PostgreSQL connection
Create a connection between the database and the API
Now that we have built the API and everything is working as expected, it is necessary to create a connection between the database and the API.
For that, we need to create a file for this matter (db.config.js
), a configuration file in which you connect to PostgreSQL with the help of a connection string.
Below we are using pg
to create a connection pool, which is basically a cache of database connections maintained for efficient reuse in future database requests.
To check if the connection is working, let’s create a route for the products, in this case, on index.js
. See how to do it below:
Step 5: Externalize Environment Variables
If you already tried to test the request above, you noticed that it won’t work on its own. Oh no! Why? Now what?
This happened because we are using the dotenv library, as you can see in the first line of code of db.config.js
— “require(‘dotenv’).config
”. This library loads variables from a .env
file, which we haven’t yet set up, into process.env
.
This is an important step because this kind of configuration needs to be stored outside of our code.
Create a .env file
To do so, create a .env
file on /server
folder and put the value of the variables there.
Now, make a GET request to http://localhost:9000/products and make sure your server is running. You’ll see the inserted products.
Step 6: Make API calls from UI
To deliver responses to client requests, our UI needs to get data from the API by making API calls. We have two options to do this:
- Axios (i.e., the option chosen for this article);
- Fetch.
Change client package.json
To let the client-side use the server-side for requests, add the code line “proxy”: “http://localhost:9000”
to the client package.json
file.
Modify client App.js file
Now, let’s modify the App.js
file in the /client
folder to allow the UI to get the data from the API and display it to the client.
This file imports axios, so we can communicate with server-side from client-side. Then, it also imports useState and useEffect:
- useState: A React hook that allows declaring a state variable and its setter function to update the state;
- useEffect: A React hook that allows declaring an effect that should run after every render, in this case, or when certain values change.
To make the response data more visually appealing, I decided to display it in a table, then I installed bootstrap (npm i bootstrap
), imported it into the project, and used some of its classes.
Run the client-side
To run the client-side, just run npm start
on the terminal.
By following all these steps above (from 1 to 6), you’ll get something like the image below, which is the result of this PERN Stack implementation: a table with the products available in the database.
Step 7: Building UI
You have the basis for building an application with the PERN Stack. As for how to build your UI, this part is up to you. However, here are some tips about how to organize your project using:
- Components: reusable code for creating UI;
- Pages: represent different routes or sections of the application, and are responsible for rendering the content for a specific route;
- Contexts: way to pass data through the component tree;
- Services: API requests.
BONUS Content: How to Dockerize everything
Finally, to end this example, since I was using docker images, it makes sense to create images for both front-end and back-end.
Step 1: Create Dockerfiles
Create a Dockerfile
in each directory,/server
and /client
. These files will be used to build both Docker images, which can be then run as Docker containers.
Dockerfile for server-side:
Dockerfile for client-side:
Step 2: Create .dockerignore files
Next, for a cleaner installation of Node packages, let’s create a .dockerignore
file in both /server
and /client
folders. In this case, this file will ignore the node_modules
directory from being copied to the containers, since we already run npm install
on Dockerfile
.
.dockerignore for both:
Step 3: Build the docker images
You now have the required files, but you need to build the images by running the next commands on the folders /server
and /client
, respectively:
docker build -t server:latest .
docker build -t client:latest .
Step 4: Create containers
And then run the images to create the containers:
docker run --name <container_name> -p 9000:9000 -d server
docker run --name <container_name> -p 3000:3000 -d client
Now that you have all the containers needed for the PERN Stack to work, you’ll notice nothing is working because the connections between the containers are broken: postgresql ← ❌ → server-side and server-side ← ❌ → client-side.
To fix this, we could inspect the container’s IP addresses with docker inspect <container-name>
and change the .env
file (PG_HOST
) from the server-side and package.json
(proxy
) from the client-side with the respective values.
However, there's a better way to do it…
Step 5: Create docker bridge network
Create a docker bridge network, so that all containers share the same network.
docker network create <network_name>
Step 6: Connect the containers
Connect the three containers to the network by running the following command for each one.
docker network connect <network_name> <container_name>
Step 7: Last configurations
Change the .env
file (server-side) and package.json
(client-side) with the name of the containers to connect. And finally, re-build and rerun the containers.
Now, just check if everything is working!
Now that all the steps are made: CONGRATS! You now have a basic dockerized PERN Stack application working!
This guide provided you with the essentials for how to build an application with the PERN Stack, but you can explore and do much more, without any limits. I’ll leave a link here with some examples of what you can do with the PERN Stack. Feel free to explore and expand on this basic example.
Keep in mind that this is just a basic example and that things may vary depending on your needs, requirements, and desires (e.g., you could use Nginx to expose your project or use Git for version control in a larger project).
Remember, the possibilities are endless and the sky is the limit! 🚀
If you enjoy working at a large scale on projects with global impact and if you like a real challenge, feel free to reach out to us at xgeeks! We are growing our team, and you might be the next one to join this group of talented people 😉
Check out our social media channels if you want to get a sneak peek of life at xgeeks! See you soon!