In this tutorial we'll integrate PostgreSQL into our REST API that we built in part 1 of these series. If you haven't checked out that part yet, I advice you to do so before continuing with this tutorial.
Prerequisites
In order to be able to follow along you will need:
Project Structure
First pull down this github repo.
Then install it by running npm init in a terminal within the project root.
Then create these new files ./config/db.js, ./database.js and ./setup.js in the root folder.
This means that your project root should now look like so:
Setting up PostgreSQL
Follow this tutorial in order to setup the PostgreSQL environment on your computer. You will be ready in no time!
When you're finished with that short tutorial, keep in mind that you'll need to remember the username, password and database name later in this tutorial.
Creating the table & columns
We'll use pgAdmin in order to create a message table in which we will store our message data.
So click and expand your database
Find Schemas and expand it
find public and expand it
Right click on Tables and in the popup menu select Query Tool...
Now the Query Editor should open up
We'll run a query which creates a table called messages with two columns in it called id and text.
Copy/Paste the following query there
Query
DROP TABLE IF EXISTS messages;
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
text VARCHAR(255)
);
Click the little play icon or press f5 to run the query.
Next right click on Tables in the menu to the left and click Refresh...
You should now see the table and the two columns id and text that we just created.
Well done! Now we have successfully added the messages table in which we will store and read messages through our API.
Preparing the API
Now that you've created and setup the database it's time that we make use of it!
Well start of by modifying adding some code to the file ./config/db.js.
Replace yourusername with your database user.
Replace youruserpassword with your database user password.
Replace yourdatabasename with your database name.
./config/db.js
module.exports = {
host: "localhost",
user: "yourusername",
password: "youruserpassword",
database: "yourdatabasename",
};
Then we'll modify the file database.js in our project root. It should then look like this:
database.js
const CONFIG = require("./config/db");
const Pool = require("pg").Pool;
const pool = new Pool({
...CONFIG,
port: 5432,
});
module.exports = pool;
We require our database configuration as well as the pg dependency we installed earlier.
We then create a connection pool by initializing a new Pool that we store in a variable called pool.
We then destruct our properties from the CONFIG object into the new Pool calls argument object. We also apply the PostgreSQL port. This is everything needed to connect to PostgreSQL.
We then export the connection pool so that we can use it in our route functions to request data from PostgreSQL.
Now we are done with database.js. Good job so far! Next - this is what I want you to do:
Save the changes and head over to index.js
Import the connection from database.js and store it in a variable called db.
We can also remove the defined MESSAGES variable now, since we're going to replace the simulated database from the previous tutorial with a real database.
This means that we can also get rid of the file messagesTable.js so go ahead and delete that file.
With these new changes made, the first part of index.js (before the actual routes/endpoints are defined) is finished.
index.js
const express = require("express");
const db = require("./database");
const app = express();
const port = 3000;
app.use(express.json());
Migrating POST request handlers
Now the time has come to migrate our request handlers that we created in the last section. Yay!
There's going to be quite a bit of changes for each handler so my advice is that you copy/paste the upcoming code snippets and replace them with the old ones in your project and then after each snippet, I'll explain in detail what we're doing. Note that since I've already explained how the app request methods and their callbacks work, I'll focus on the code within the callback functions. Incase you need a recap, have a look here. Sound good? Ok. Let's go!
So let's begin with our post request handler.
index.js
app.post("/", function (req, res) {
const query = "INSERT INTO messages (text) VALUES ($1)";
const values = [req.body.text];
db.query(query, values, function (err, result) {
if (err) throw err;
const message = { id: result.insertId, text: values[0] };
res.status(200).send(message);
});
});
We define a query variable in which we store an SQL query string which will insert data into the text column of our messages table in our database, once we run it. Note that we've specified a placeholder $1. All the placeholders that we use in a query string will be replaced with the values stored in the array that we've defined underneath, called values. So in this case we have 1 placeholder in the query string called $1, meaning it will be replaced with the first index of the values array, which in this case is the request body text that we passed to this endpoint (the message that we want to save in the database).
If we'd want to insert multiple values, we could use more placeholders in the correct positions of the SQL query string and have more indexes in the values array and they would be replaced depending on placeholder index and values index. But in this case, we only have 1 placeholder to replace with a value.
You may ask yourself; "Ok, but why the heck can't I just use a template literal and define an expression instead of a placeholder that will be replaced later on?"
Well, the short answer is... We want to avoid being targets for SQL injection attacks.
I will not go into detail on injection attacks but basically it means that a malicious user could modify data in our database without our permission.Next, we use the query method of our connection that we stored in a variable called db that we previously defined. This method takes 3 arguments.
The query which holds the PostgreSQL statement to create a new row in our messages table and populate the column value text with a value.
The values array which indexes will replace the the placeholders of the query.
A callback function that will be called as soon as PostgreSQL has completed the operation.Now, in the callback function we have two parameters err and result. If we get an error we throw that error.
If we didn't get an error, we can assume that we have a result from PostgreSQL. We get the insertId, from the result object, which is the index of the last row inserted into the messages table of our database. We use this insertId as well as the first values index, which will be the request.body.text, to construct a message object that we send back as a response with a response status code of 200.
Migrating GET request handlers
Time to migrate our GET request handlers. As you probably remember from the last tutorial of these series, we have two types of GET endpoints. One for getting all the messages and the second for getting a message with a specific id.
So let's begin with the one for getting all the messages.
index.js
app.get("/", function (req, res) {
const query = "SELECT * FROM messages";
db.query(query, function (err, result) {
if (err) throw err;
const messages = result.rows;
res.status(200).send(messages);
});
});
In the callback function, we define a SQL query string which will tell MySQL to give us all rows from the messages table in our database.
In the db.query call we pass the query as well as a callback function. Note that we do not pass any values here like in the POST request - simply because we do not have any data to insert. This time we only want to get everything in the messages table.
As usual we throw and error if there is one. If not, we get the result.rows and for clarity we save it in a variable called messages.
Just like before we send all those messages back as a response with a status code of 200.
Now it's time to migrate our second GET request endpoint.
index.js
app.get("/:id", function (req, res) {
const id = parseInt(req.params.id);
const query = "SELECT * FROM messages WHERE id = $1";
const values = [id];
db.query(query, values, function (err, result) {
if (err) throw err;
const found = result.rows;
if (found.length > 0) {
res.status(200).send(found);
} else {
res.status(404).send({ msg: `No message with id ${id} found!` });
}
});
});
We get the id from req.params.id which will be of string type and parse it into an integer that we store in a id variable.
We create a query and a values array.
Similar to the POST request, we pass query and values as arguments to the db.query call as well as a callback function.
We throw an error if there is one, as usual.
If there's no error, we create a variable found and store the result.rows in it for clarity.
Since the result.rows is always an array, and we stored it in found, we create a condition to check if there were any found items (if the length of the found array is greater than 0). If it is, we send the found array as a response with a statuscode of 200.
If on the other hand found is an empty array, meaning we couldn't find anything in our database based on the id that we passed, we send an object as a response with the statuscode 404 (not found), containing a error message.
Migrating PUT request handlers
Time to migrate the PUT request endpoint.
index.js
app.put("/:id", function (req, res) {
const id = parseInt(req.params.id);
const query = "UPDATE messages SET text = $1 WHERE id = $2";
const values = [req.body.text, id];
db.query(query, values, function (err, result) {
if (err) throw err;
const updated = [{ id: values[1], message: values[0] }];
if (result.rowCount === 0)
return res
.status(404)
.send({ msg: `No message with id ${values[1]} found!` });
res.status(200).send(updated);
});
});
A lot of the syntax here might look similar to the previous request handlers that we've migrated. How ever, note here that the query uses two placeholders and therefore two indexes in the values array, which is the req.body.text (t updated message text) as well as the id for the message that we want to update the message text for.
In the callback we handle potential errors as usual.
If there's no error, we store an object that consist of values data, in an array that we pass to a variable called updated. This is the data which we will send as a response in case we were able to update the specified message in MySQL.
Next we create a condition where we check if result.rowCount is equal to zero. This means that we were unable to find and update any message in our database with the specified id and therefore result.rowCount will be zero since no rows where affected. If it is zero we'll return here and send an error message as a response.
If on the other hand there were more than zero affected rows, which means that we actually succeded to find and update a message with the specified id, we send a response with the status code 200 and pass the updated array within that response.
Migrating DELETE request handlers
Almost done now! Time for migration of the final endpoint where we handle DELETE requests.
index.js
app.delete("/:id", function (req, res) {
const id = parseInt(req.params.id);
const query = "DELETE FROM messages WHERE id = $1";
const values = [id];
db.query(query, values, function (err, result) {
if (err) throw err;
const deleted = [{ id: values[0] }];
if (result.rowCount === 0)
return res
.status(404)
.send({ msg: `No message with id ${values[0]} found!` });
res.status(200).send(deleted);
});
});
As you might notice, the callback function of this route is somewhat similar to the PUT request. We get the id, create the query and add the id in the values array which will replace the placeholder of the query.
In the db.query function call's callback we handle the error.
If no error, we create a deleted array which holds an object with an id property that contain the id that we have requested to DELETE.
If there weren't any affected rows (result.rowCount), we return and send a response with a status code of 404 and an error message.
If, there were affected rows, we send the deleted array as a response with a status code of 200.
Testing the endpoints using cURL
If you have curl installed on your computer you can follow along in this section. Note that all Mac computers, with operating system version equal to or greater than OSX, comes with cURL pre-installed.
Obviously your Express server needs to be running before you try to make any requests to it. So if it's not already running, start it.
Terminal
npm run start
Executing a POST request
Next we'll make a POST request from a terminal. We'll send the request to http://127.0.0.1:3000 which is equal to localhost, at port 3000 which is the port that we use for our application. We pass a JSON request body with the message text. We also pass a request header that defines that the data that we're sending is JSON data.
Terminal
curl -X POST http://127.0.0.1:3000/ -d '{"text":"New message"}' -H "Content-Type: application/json"
When sending this request from your terminal, you should get a response back that looks like this:
Terminal Response
{"id":1,"text":"New message"}
Cool! So now we've successfully created a new message with a POST request!
Executing GET requests
Now let's try to make a GET request, fetching all the messages that our server has stored so far. We should be able to fetch the 1 message that we added with the POST request. And of course if you were to add more messages, these messages should also be fetched with this request.
So let's test our GET endpoint which will return all the messages stored in the database so far.
Terminal
curl -X GET http://127.0.0.1:3000/
Boom! Here's all of our messages (currently just one if you haven't sent multiple POST requests).
Terminal Response
[{"id":1,"text":"New message"}]
Next, let's try the GET endpoint which will return a specific message based on the passed request parameter id.
So, this time, let's pass a request parameter 1 when making the GET request.
Terminal
curl -X GET http://127.0.0.1:3000/1
Voila! We get the message with the requested id.
Terminal Response
[{"id":1,"text":"New message"}]
But what if we request an id that doesn't exist in our database? Let's see.
Terminal
curl -X GET http://127.0.0.1:3000/1337
Ah! We get back the error object that we previously defined - telling us that the message with id 1337 was not found. Perfect!
Terminal Response
{"msg":"No message with id 1337 found!"}
Executing a PUT request
Now let's try updating a message. Let's update message with id 1.
Terminal
curl -X PUT http://127.0.0.1:3000/1 -d '{"text":"Updated message"}' -H "Content-Type: application/json"
We get back our updated message as a response. Awesome!
Terminal Response
[{"id":1,"text":"Updated message"}]
Executing a DELETE request
Finally, let's try to delete a message. Let's delete message with id 1.
Terminal
curl -X DELETE http://127.0.0.1:3000/1
Works like a charm!
Terminal Response
[{"id":1}]
Testing the endpoints using Postman
So if you were unable to test your endpoints with cURL, or just didn't feel like doing so, the time has come to test our endpoints using Postman.
Let's start off by making sure that our Express server is running. As you already know, we run it by executing the following command from our project root.
Terminal
npm run start
Executing a POST request
So, open up Postman and from the top menu, select File > New Tab.
Then select POST from the dropdown menu and input http://127.0.0.1:3000/ as the request url. Note that this is the same as inputting http://localhost:3000/ so choose whatever you prefer.
Next click on Body just below the request url bar.
Select raw in the radio button selection and in the dropdown to the right of the radio buttons, select JSON. Then press the Send button.
Now in the bottom section of Postman, you should see a response consisting of the message that we just created, as well as a Status of "200 OK".
Awesome! We've just created our first message by sending our first POST request.
Executing GET requests
Now let's try to make a GET request, fetching all the messages that our database has stored so far. Since we've only made one POST request so far, we should be able to get that message. How ever if you've made more than one POST request to create more messages, you should be able to fetch them all from this GET request.
So let's test our GET endpoint which will return all the messages stored in the database so far. We can do this by simply just changing the request type to GET from the top left drop-down menu. Note that we can leave the request body data as is - it will not be sent with the GET request anyways. Press Send when you've applied that change to the request type.
Now we should get some data back which we can find in the response body section of Postman.
Cool! So we were able to get all the messages that we had stored in our database (only one in my case, since I've only made 1 POST request so far). Now let's test our other GET endpoint which will return a specific message based on the request parameter passed with the request.
Simply add an id to the request URL. Let's get message with id 1. Then press Send again.
This time the response body should look like so (Yes I know it looks exactly the same as the previous response if you haven't made any extra POST requests):
Doesn't it just feel good when you get whatever you request? Love it.
Ok, so what happens if we try to request a message with an id that doesn't exist? Let's try it out. Let's use 1337 as an id.
Press Send and see what happens.
Wonderful! We're getting our error message that we defined earlier, with the statuscode of 404 which means that the item that we requested was not found on the server.
Executing a PUT request
Now let's try updating a message. Let's update message with id 1.
So first, change the request type to PUT and make sure that the request parameter is 1 in the request url.
Then before sending this, let's modify the request body.
Now press Send.
And the response body should now return our updated message.
Executing a DELETE request
Finally, let's try to delete a message. Let's delete message with id 1. So change the request type to DELETE and make sure that you pass the id 1 as a request parameter.
We get the deleted message back as a response within the response body.
Wooha! No message with id 1 in our database, which means that our DELETE request worked!
Final words
You're now one step closer to being table to construct a fully functional REST api using Node.js, Express & PostgreSQL. In the next tutorial of these series, we will take the API that we just built to the next level and deploy it to a cloud service.
You can find the final source code for this tutorial here.