{"id":318,"date":"2026-03-25T12:00:49","date_gmt":"2026-03-25T16:00:49","guid":{"rendered":"https:\/\/logrocketstaging.wordpress.com\/?p=182"},"modified":"2026-03-27T10:41:40","modified_gmt":"2026-03-27T14:41:40","slug":"crud-rest-api-node-js-express-postgresql","status":"publish","type":"post","link":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/","title":{"rendered":"CRUD REST API with Node.js, Express, and PostgreSQL"},"content":{"rendered":"<!DOCTYPE html>\n<html><p><em><strong>Editor\u2019s note:<\/strong> Updated by <a href=\"https:\/\/blog.logrocket.com\/author\/emmanueljohn\/\">Emmanuel John<\/a> in January 2026 to reflect modern Node.js practices. Changes include migrating to ES modules, replacing <code>body-parser<\/code> with built-in middleware, using async\/await for queries, and adding guidance on environment variables, watch mode, and API security enhancements.<\/em><\/p><img loading=\"lazy\" decoding=\"async\" width=\"895\" height=\"597\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png\" class=\"attachment-full size-full wp-post-image\" alt=\"Crud Rest Api With Node Js Express And Postgresql\" srcset=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png 895w, https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL-300x200.png 300w, https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL-768x512.png 768w\" sizes=\"auto, (max-width: 895px) 100vw, 895px\">\n<p>Working with APIs to facilitate communication between software systems is crucial for modern web developers. In this tutorial, we\u2019ll create a CRUD RESTful API in a Node.js environment that runs on an Express server and uses a PostgreSQL database.<\/p>\n<p>We\u2019ll also walk through connecting an Express server with PostgreSQL using <a href=\"https:\/\/node-postgres.com\">node-postgres<\/a>. Our API will be able to handle the HTTP request methods that correspond to the PostgreSQL database from which the API gets its data. You\u2019ll also learn how to install PostgreSQL and work with it through the CLI.<\/p>\n<p>Our goal is to allow CRUD operations \u2014 <code>GET<\/code>, <code>POST<\/code>, <code>PUT<\/code>, and <code>DELETE<\/code> \u2014 on the API, which will run the corresponding database commands. To do so, we\u2019ll set up a route for each endpoint and a function for each query.<\/p>\n<p>To follow along with this tutorial, you\u2018ll need:<\/p>\n<ul>\n<li>Familiarity with the JavaScript syntax and fundamentals<\/li>\n<li>Basic knowledge of working with the command line<\/li>\n<li>Node.js and npm installed<\/li>\n<\/ul>\n<p>The complete code for the tutorial is available in this <a href=\"https:\/\/github.com\/nemo0\/node-postgres-crud-api\">GitHub repo<\/a>. Let\u2019s get started!<\/p>\n<h2 id=\"what-is-a-restful-api\">What is a RESTful API?<\/h2>\n<p>Representational State Transfer (REST) defines a set of standards for web services.<br>\nAn API is an interface that software programs use to communicate with each other. Therefore, a RESTful API is an API that conforms to the REST architectural style and constraints.<br>\nREST systems are stateless, scalable, cacheable, and have a uniform interface.<\/p>\n<h2 id=\"what-is-a-crud-api\">What is a CRUD API?<\/h2>\n<p>When building an API, you want your model to provide four basic functionalities. It should be able to create, read, update, and delete resources. This set of essential operations is commonly referred to as CRUD.<\/p>\n<p>RESTful APIs most commonly utilize HTTP requests. Four of the most common HTTP methods in a REST environment are <code>GET<\/code>, <code>POST<\/code>, <code>PUT<\/code>, and <code>DELETE<\/code>, which are the methods by which a developer can create a CRUD system:<\/p>\n<ul>\n<li><code><strong>Create<\/strong><\/code>\u2013 Use the <code>HTTP POST<\/code> method to create a resource in a REST environment<\/li>\n<li><strong><code>Read<\/code><\/strong>\u2013 Use the <code>GET<\/code> method to read a resource, retrieving data without altering it<\/li>\n<li><strong><code>Update<\/code><\/strong>\u2013 Use the <code>PUT<\/code> method to update a resource<\/li>\n<li><code><strong>Delete<\/strong><\/code>\u2013 Use the <code>DELETE<\/code> method to remove a resource from the system<\/li>\n<\/ul>\n<h2 id=\"what-is-express\">What is Express?<\/h2>\n<p>According to the official <a href=\"https:\/\/expressjs.com\">Express documentation<\/a>, Express is a fast, unopinionated, minimalist web framework for Node.js. Express is one of the most popular frameworks for Node.js. In fact, each E in the MERN, MEVN, and MEAN stacks stands for Express.<\/p>\n<p>Although Express is minimalist, it\u2019s also very flexible. This supports the <a href=\"https:\/\/blog.logrocket.com\/express-middleware-a-complete-guide\/\">development of various Express<\/a> middlewares that you can use to address almost any task or problem imaginable.<\/p>\n<h2 id=\"what-is-postgresql\">What is PostgreSQL?<\/h2>\n<p>PostgreSQL, commonly referred to as Postgres, is a free and open source relational database management system. You might be familiar with a few other similar database systems, like MySQL, Microsoft SQL Server, or MariaDB, which compete with PostgreSQL.<\/p>\n<p>PostgreSQL is a robust relational database that has been around since 1997. It\u2019s available on all major operating systems \u2014 Linux, Windows, and macOS. Since PostgreSQL is known for stability, extensibility, and standards compliance, it\u2019s a popular choice for developers and companies.<\/p>\n<p>It\u2019s also possible to create a Node.js <a href=\"https:\/\/blog.logrocket.com\/using-sequelize-with-typescript\/\">RESTful CRUD API using Sequelize<\/a>. Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite, and Microsoft SQL Server.<br>\nFor more on how to use Sequelize in a Node.js REST API, check out the video tutorial below:<\/p>\n<p><iframe loading=\"lazy\" title=\"Building an API in Node.js with Express and Sequelize ORM\" width=\"500\" height=\"375\" src=\"https:\/\/www.youtube.com\/embed\/VDgXAw7VynQ?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<h2 id=\"what-is-node-postgres\">What is node-postgres?<\/h2>\n<p>node-postgres, or pg, is a nonblocking PostgreSQL client for Node.js. Essentially, node-postgres is a collection of Node.js modules for interfacing with a PostgreSQL database.<br>\nnode-postgres supports many features, including callbacks, promises, async\/await, connection pooling, prepared statements, cursors, rich type parsing, and C\/C++ bindings.<\/p>\n<h2 id=\"creating-a-postgresql-database\">Creating a PostgreSQL database<\/h2>\n<p>We\u2019ll begin this tutorial by installing PostgreSQL, creating a new user, creating a database, and initializing a table with a schema and some data.<\/p>\n<h3 id=\"installation\">Installation<\/h3>\n<p>If you\u2019re using Windows, download a <a href=\"https:\/\/www.postgresql.org\/download\/windows\/\">Windows installer<\/a> of PostgreSQL.<br>\nIf you\u2019re using a Mac, this tutorial assumes you have <a href=\"https:\/\/brew.sh\">Homebrew<\/a> installed on your computer as a package manager for installing new programs. If you don\u2019t, simply click on the link and follow the instructions.<\/p>\n<p>This tutorial targets PostgreSQL 17, the current stable release. Open up the terminal and install it with <code>brew<\/code>:<\/p>\n<pre class=\"language-bash hljs\">brew install postgresql@17<\/pre>\n<p><strong>Note:<\/strong> Homebrew now uses versioned formulae (e.g <code>postgresql@17<\/code>) rather than the unversioned <code>postgresql<\/code> tap. If you have an older version already installed, you can upgrade with <code>brew upgrade postgresql@17<\/code> or install the newer formula alongside it. |<\/p>\n<p>After the installation is complete, we\u2019ll want to get <code>postgresql<\/code> up and running, which we can do with <code>services start<\/code>:<\/p>\n<pre class=\"language-bash hljs\">brew services start postgresql@17\n==&gt; Successfully started `postgresql@17` (label: homebrew.mxcl.postgresql@17)<\/pre>\n<p>If at any point you want to stop the <code>postgresql<\/code> service, you can run <code>brew services stop postgresql<\/code>.<\/p>\n<p>You may also need to add the PostgreSQL 17 binaries to your shell\u2019s PATH so that <code>psql<\/code> is available:<\/p>\n<pre class=\"language-bash hljs\">echo 'export PATH=\"\/opt\/homebrew\/opt\/postgresql@17\/bin:$PATH\"' &gt;&gt; ~\/.zshrc\nsource ~\/.zshrc<\/pre>\n<p>With PostgreSQL installed, let\u2019s next connect to the <code>postgres<\/code> command line where we can run SQL commands.<\/p>\n<h3 id=\"postgresql-command-prompt\">PostgreSQL command prompt<\/h3>\n<p><code>psql<\/code> is the PostgreSQL interactive terminal. Running <code>psql<\/code> will connect you to a PostgreSQL host. Running <code>psql --help<\/code> will give you more information about the available options for connecting with <code>psql<\/code>:<\/p>\n<ul>\n<li><code><strong>-h<\/strong><\/code><strong> or <code>--host=HOSTNAME <\/code><\/strong>&nbsp;\u2013 The database server host or socket directory; the default is <code>local socket<\/code><\/li>\n<li><strong><code>-p<\/code> or <code>--port=PORT <\/code>&nbsp;\u2013<\/strong>&nbsp;The database server port; the default is <code>5432<\/code><\/li>\n<li><strong><code>-U<\/code> or <code>--username=USERNAME<\/code>\u2013 <\/strong>The database username; the default is <code>your_username<\/code><\/li>\n<li><strong><code>-w<\/code> or <code>--no-password<\/code>\u2013 <\/strong>&nbsp;Never prompt for password<\/li>\n<li><strong><code>-W<\/code> or <code>--password<\/code>\u2013 <\/strong>Force password prompt, which should happen automatically<\/li>\n<\/ul>\n<p>We\u2019ll connect to the default <code>postgres<\/code> database with the default login information and no option flags:<\/p>\n<pre class=\"language-bash hljs\">psql postgres<\/pre>\n<p>You\u2019ll see that we\u2019ve entered into a new connection. We\u2019re now inside <code>psql<\/code> in the <code>postgres<\/code> database. The prompt ends with a <code>#<\/code> to denote that we\u2019re logged in as the superuser, or root:<\/p>\n<pre class=\"language-bash hljs\">postgres=#<\/pre>\n<p>Commands within <code>psql<\/code> start with a backslash <code>\\<\/code>. To test our first command, we can check what database, user, and port we\u2019ve connected to using the <code>\\conninfo<\/code> command:<\/p>\n<pre class=\"language-bash hljs\">postgres=# \\conninfo\nYou are connected to database \"postgres\" as user \"your_username\" via socket in \"\/tmp\" at port \"5432\".<\/pre>\n<p>The reference table below includes a few common commands that we\u2019ll use throughout this tutorial:<\/p>\n<pre class=\"language-bash hljs\">\\q: Exit psql connection\n\\c: Connect to a new database\n\\dt: List all tables\n\\du: List all roles\n\\list: List databases<\/pre>\n<p>Let\u2019s create a new database and user so we\u2019re not using the default accounts, which have superuser privileges.<\/p>\n<h3 id=\"creating-a-role-in-postgres\">Creating a role in Postgres<\/h3>\n<p>First, we\u2019ll create a role called <code>me<\/code> and give it a password of <code>password<\/code>. A role can function as a user or a group. In this case, we\u2019ll use it as a user:<\/p>\n<pre class=\"language-sql hljs\">postgres=# CREATE ROLE me WITH LOGIN PASSWORD 'password';<\/pre>\n<p>We want <code>me<\/code> to be able to create a database:<\/p>\n<pre class=\"language-sql hljs\">postgres=# ALTER ROLE me CREATEDB;<\/pre>\n<p>You can run <code>\\du<\/code> to list all roles and users:<\/p>\n<pre class=\"language-bash hljs\">me          | Create DB                           | {}\npostgres    | Superuser, Create role, Create DB   | {}<\/pre>\n<p>Now, we want to create a database from the <code>me<\/code> user. Exit from the default session with <code>\\q<\/code> for quit:<\/p>\n<pre class=\"language-bash hljs\">postgres=# \\q<\/pre>\n<p>We\u2019re back in our computer\u2019s default terminal connection. Now, we\u2019ll connect <code>postgres<\/code> with <code>me<\/code>:<\/p>\n<pre class=\"language-bash hljs\">psql -d postgres -U me<\/pre>\n<p>Instead of <code>postgres=#<\/code>, our prompt now shows <code>postgres=&gt;<\/code> , meaning we\u2019re no longer logged in as a superuser.<\/p>\n<h3 id=\"creating-a-database-in-postgres\">Creating a database in Postgres<\/h3>\n<p>We can create a database with the SQL command as follows:<\/p>\n<pre class=\"language-sql hljs\">postgres=&gt; CREATE DATABASE api;<\/pre>\n<p>Use the <code>\\list<\/code> command to see the available databases:<\/p>\n<pre class=\"language-bash hljs\">Name    |    Owner    | Encoding |   Collate   |    Ctype    |\napi     | me          | UTF8     | en_US.UTF-8 | en_US.UTF-8 |<\/pre>\n<p>Let\u2019s connect to the new <code>api<\/code> database with <code>me<\/code> using the <code>\\c<\/code> connect command:<\/p>\n<pre class=\"language-bash hljs\">postgres=&gt; \\c api\nYou are now connected to database \"api\" as user \"me\".\napi=&gt;<\/pre>\n<p>Our prompt now shows that we\u2019re connected to <code>api<\/code>.<\/p>\n<h3 id=\"creating-a-table-in-postgres\">Creating a table in Postgres<\/h3>\n<p>Finally, in the <code>psql<\/code> command prompt, we\u2019ll create a table called <code>users<\/code> with three fields, two <code>VARCHAR<\/code> types, and an auto-incrementing <code>PRIMARY KEY<\/code> ID:<\/p>\n<pre class=\"language-sql hljs\">api=&gt;\nCREATE TABLE users (\n  ID SERIAL PRIMARY KEY,\n  name VARCHAR(30),\n  email VARCHAR(30)\n);<\/pre>\n<p>Make sure not to use the backtick <code>`<\/code> character when creating and working with tables in PostgreSQL. While backticks are allowed in MySQL, they\u2019re not valid in PostgreSQL. Also, ensure that you do not have a trailing comma in the CREATE TABLE command.<\/p>\n<p>Let\u2019s add some data to work with by adding two entries to <code>users<\/code>:<\/p>\n<pre class=\"language-sql hljs\">INSERT INTO users (name, email)\n  VALUES ('Jerry', 'jerry@example.com'), ('George', 'george@example.com');<\/pre>\n<p>Let\u2019s make sure that the information above was correctly added by getting all entries in <code>users<\/code>:<\/p>\n<pre class=\"language-sql hljs\">api=&gt; SELECT * FROM users;\nid |  name  |       email        \n----+--------+--------------------\n  1 | Jerry  | jerry@example.com\n  2 | George | george@example.com<\/pre>\n<p>Now, we have a user, database, table, and some data. We can begin building our Node.js RESTful API to connect to this data, stored in a PostgreSQL database.<\/p>\n<p>At this point, we\u2019re finished with all of our PostgreSQL tasks, and we can begin setting up our Node.js app and Express server.<\/p>\n<h2 id=\"setting-up-an-express-server\">Setting up an Express server<\/h2>\n<p>To set up a Node.js app and Express server, first create a directory for the project to live in:<\/p>\n<pre class=\"language-bash hljs\">mkdir node-api-postgres\ncd node-api-postgres<\/pre>\n<p>You can either run <code>npm init -y<\/code> to create a <code>package.json<\/code> file, or copy the code below into a <code>package.json<\/code> file:<\/p>\n<pre class=\"language-json hljs\">{\n  \"name\": \"node-api-postgres\",\n  \"version\": \"1.0.0\",\n  \"description\": \"RESTful API with Node.js, Express, and PostgreSQL\",\n  \"main\": \"index.js\",\n  \"type\": \"module\",\n  \"license\": \"MIT\"\n}<\/pre>\n<p>We\u2019ll want to install Express for the server and node-postgres to connect to PostgreSQL:<\/p>\n<pre class=\"language-bash hljs\">npm i express pg<\/pre>\n<p>Now, we have our dependencies loaded into <code>node_modules<\/code> and <code>package.json<\/code>.<\/p>\n<p>Create an <code>index.js<\/code> file, which we\u2019ll use as the entry point for our server. Since we set <code>\"type\": \"module\"<\/code> in <code>package.json<\/code>, we can use ESM <code>import<\/code> syntax. At the top, import the <code>express<\/code> module and set our <code>app<\/code> and <code>port<\/code> variables. Since Express 4.16+, the <code>express.json()<\/code> and <code>express.urlencoded()<\/code> <a href=\"https:\/\/expressjs.com\/en\/resources\/middleware\/body-parser.html\">middlewares<\/a> are built in, so no separate <code>body-parser<\/code> package is needed:<\/p>\n<pre class=\"language-javascript hljs\">import express from 'express'\nconst app = express()\nconst port = 3000\n\napp.use(express.json())\napp.use(\n  express.urlencoded({\n    extended: true,\n  })\n)<\/pre>\n<p>We\u2019ll tell a route to look for a <code>GET<\/code> request on the root <code>\/<\/code> URL and return some JSON:<\/p>\n<pre class=\"language-javascript hljs\">app.get('\/', (request, response) =&gt; {\n  response.json({ info: 'Node.js, Express, and Postgres API' })\n})<\/pre>\n<p>Now, set the app to listen on the port you set:<\/p>\n<pre class=\"language-javascript hljs\">app.listen(port, () =&gt; {\n  console.log(`App running on port ${port}.`)\n})<\/pre>\n<p>From the command line, we can start the server by hitting <code>index.js<\/code>:<\/p>\n<pre class=\"language-bash hljs\">node index.js\nApp running on port 3000.<\/pre>\n<p>Restarting the server manually after every change gets tedious fast. Node 18+ ships a built-in watch mode. Run <code>node --watch index.js<\/code> and Node will automatically restart whenever a file changes. Alternatively, install nodemon (<code>npm install -D nodemon<\/code>) and run <code>npx nodemon index.js<\/code> for a slightly richer experience with colored output and custom ignore rules.<\/p>\n<p>Go to <code>http:\/\/localhost:3000<\/code> in the URL bar of your browser, and you\u2019ll see the JSON we set earlier:<\/p>\n<pre class=\"language-json hljs\">{\n  info: \"Node.js, Express, and Postgres API\"\n}<\/pre>\n<p>The Express server is running now, but it\u2019s only sending some static JSON data that we created. The next step is to connect to PostgreSQL from Node.js to be able to make dynamic queries.<\/p>\n<h2 id=\"connecting-to-a-postgres-database-using-a-client\">Connecting to a Postgres database using a Client<\/h2>\n<p>A popular client for accessing Postgres databases is the pgAdmin client. The pgAdmin application is available for various platforms. If you want to have a graphical user interface for your Postgres databases, you can go to the <a href=\"https:\/\/www.pgadmin.org\/download\/\">download page<\/a> and download the necessary package.<\/p>\n<p>Creating and querying your database using pgAdmin is simple. You need to click on the Object option available on the top menu, select Create, and choose Database to create a new connection. All the databases are available on the side menu. You can query or run SQL queries efficiently by selecting the proper database:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Create-query-database.png\" alt=\"Screenshot Of Process Using Pgadmin To Create A Query Database\"><\/p>\n<h2 id=\"connecting-to-a-postgres-database-from-nodejs\">Connecting to a Postgres database from Node.js<\/h2>\n<p>We\u2019ll use the <a href=\"https:\/\/node-postgres.com\">node-postgres<\/a> module to create a pool of connections. Therefore, we don\u2019t have to open and close a client each time we make a query.<\/p>\n<p>A popular option for production pooling would be to use <code>[pgBouncer](https:\/\/pgbouncer.github.io\/)<\/code>, a lightweight connection pooler for PostgreSQL.<\/p>\n<pre class=\"language-javascript hljs\">import pg from 'pg'\nconst { Pool } = pg\nconst pool = new Pool({\n  user: 'me',\n  host: 'localhost',\n  database: 'api',\n  password: 'password',\n  port: 5432,\n})<\/pre>\n<p>In a production environment, you would want to put your configuration details in a separate file with restrictive permissions so that it is not accessible from version control. But, for the simplicity of this tutorial, we\u2019ll keep it in the same file as the queries.<\/p>\n<p>If you want a safer setup from the start, use environment variables with <code>dotenv<\/code> instead of hardcoding credentials. Install <code>dotenv<\/code>:<\/p>\n<pre class=\"language-bash hljs\">npm install dotenv<\/pre>\n<p>Then add your DB settings to a <code>.env<\/code> file:<\/p>\n<pre class=\"language-bash hljs\">DB_USER=me\nDB_HOST=localhost\nDB_NAME=api\nDB_PASSWORD=password\nDB_PORT=5432<\/pre>\n<p>Load those values in <code>queries.js<\/code>:<\/p>\n<pre class=\"language-javascript hljs\">import 'dotenv\/config'\nimport pg from 'pg'\nconst pool = new Pool({\n  user: process.env.DB_USER,\n  host: process.env.DB_HOST,\n  database: process.env.DB_NAME,\n  password: process.env.DB_PASSWORD,\n  port: Number(process.env.DB_PORT),\n})<\/pre>\n<p>Also, add <code>.env<\/code> to <code>.gitignore<\/code> so secrets are never committed.<\/p>\n<p>The aim of this tutorial is to allow CRUD operations \u2014 <code>GET<\/code>, <code>POST<\/code>, <code>PUT<\/code>, and <code>DELETE<\/code> \u2014 on the API, which will run the corresponding database commands. To do so, we\u2019ll set up a route for each endpoint and a function corresponding to each query.<\/p>\n<h2 id=\"creating-routes-for-crud-operations\">Creating routes for CRUD operations<\/h2>\n<p>We\u2019ll create six functions for six routes, as shown below. First, create all the functions for each route. Then, export the functions so they\u2019re accessible:<\/p>\n<pre class=\"language-bash hljs\">GET: \/ | displayHome()\nGET: \/users | getUsers()\nGET: \/users\/:id | getUserById()\nPOST: \/users | createUser()\nPUT: \/users\/:id | updateUser()\nDELETE: \/users\/:id | deleteUser()<\/pre>\n<p>In <code>index.js<\/code>, we made an <code>app.get()<\/code> for the root endpoint with a function in it. Now, in <code>queries.js<\/code>, we\u2019ll create endpoints that will display all users, display a single user, create a new user, update an existing user, and delete a user.<\/p>\n<pre class=\"language-bash hljs\">GET all users<\/pre>\n<p>Our first endpoint will be a <code>GET<\/code> request. We can put the raw SQL that will touch the <code>api<\/code> database inside <code>await pool.query()<\/code>. We\u2019ll <code>SELECT<\/code> all users and order by ID.<\/p>\n<pre class=\"language-javascript hljs\">const getUsers = async (request, response) =&gt; {\n  try {\n    const results = await pool.query('SELECT * FROM users ORDER BY id ASC')\n    response.status(200).json(results.rows)\n  } catch (error) {\n    throw error\n  }\n}<\/pre>\n<p>GET a single user by ID<br>\nFor our <code>\/users\/:id<\/code> request, we\u2019ll get the custom <code>id<\/code> parameter by the URL and use a <code>WHERE<\/code> clause to display the result.<\/p>\n<p>In the SQL query, we\u2019re looking for <code>id=$1<\/code>. In this instance, <code>$1<\/code> is a numbered placeholder that PostgreSQL uses natively instead of the <code>?<\/code> placeholder that you may recognize from other variations of SQL:<\/p>\n<pre class=\"language-javascript hljs\">const getUserById = async (request, response) =&gt; {\n  const id = parseInt(request.params.id, 10)\n\n  try {\n    const results = await pool.query('SELECT * FROM users WHERE id = $1', [id])\n    response.status(200).json(results.rows)\n  } catch (error) {\n    throw error\n  }\n}<\/pre>\n<p><code>POST<\/code> a new user<br>\nThe API will take a <code>GET<\/code> and <code>POST<\/code> request to the <code>\/users<\/code> endpoint. In the <code>POST<\/code> request, we\u2019ll add a new user. In this function, we\u2019re extracting the <code>name<\/code> and <code>email<\/code> properties from the request body and inserting the values with <code>INSERT<\/code>:<\/p>\n<pre class=\"language-javascript hljs\">const createUser = async (request, response) =&gt; {\n  const { name, email } = request.body\n\n  try {\n    const results = await pool.query(\n      'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',\n      [name, email]\n    )\n    response.status(201).send(`User added with ID: ${results.rows[0].id}`)\n  } catch (error) {\n    throw error\n  }\n}<\/pre>\n<p><code>PUT<\/code> updated data in an existing user<br>\nThe <code>\/users\/:id<\/code> endpoint will also take two HTTP requests, the <code>GET<\/code> we created for <code>getUserById<\/code> and a <code>PUT<\/code> to modify an existing user. For this query, we\u2019ll combine what we learned in <code>GET<\/code> and <code>POST<\/code> to use the <code>UPDATE<\/code> clause.<\/p>\n<p>It\u2019s worth noting that <code>PUT<\/code> is idempotent, meaning the exact same call can be made over and over and will produce the same result. <code>PUT<\/code> is different than <code>POST<\/code>, in which the exact same call repeated will continuously make new users with the same data:<\/p>\n<pre class=\"language-javascript hljs\">const updateUser = async (request, response) =&gt; {\n  const id = parseInt(request.params.id, 10)\n  const { name, email } = request.body\n\n  try {\n    await pool.query('UPDATE users SET name = $1, email = $2 WHERE id = $3', [\n      name,\n      email,\n      id,\n    ])\n    response.status(200).send(`User modified with ID: ${id}`)\n  } catch (error) {\n    throw error\n  }\n}<\/pre>\n<p><code>DELETE<\/code> a user<br>\nFinally, we\u2019ll use the <code>DELETE<\/code> clause on <code>\/users\/:id<\/code> to delete a specific user by ID. This call is very similar to our <code>getUserById()<\/code> function:<\/p>\n<pre class=\"language-javascript hljs\">const deleteUser = async (request, response) =&gt; {\n  const id = parseInt(request.params.id, 10)\n\n  try {\n    await pool.query('DELETE FROM users WHERE id = $1', [id])\n    response.status(200).send(`User deleted with ID: ${id}`)\n  } catch (error) {\n    throw error\n  }\n}<\/pre>\n<h2 id=\"exporting-crud-functions-in-a-rest-api\">Exporting CRUD functions in a REST API<\/h2>\n<p>To access these functions from <code>index.js<\/code>, we\u2019ll need to export them from <code>queries.js<\/code> with named exports:<\/p>\n<pre class=\"language-javascript hljs\">export {\n  getUsers,\n  getUserById,\n  createUser,\n  updateUser,\n  deleteUser,\n}<\/pre>\n<p>Our complete <code>queries.js<\/code> file is below:<\/p>\n<pre class=\"language-javascript hljs\">import pg from 'pg'\nconst { Pool } = pg\nconst pool = new Pool({\n  user: 'me',\n  host: 'localhost',\n  database: 'api',\n  password: 'password',\n  port: 5432,\n})\nconst getUsers = async (request, response) =&gt; {\n  try {\n    const results = await pool.query('SELECT * FROM users ORDER BY id ASC')\n    response.status(200).json(results.rows)\n  } catch (error) {\n    throw error\n  }\n}\n\nconst getUserById = async (request, response) =&gt; {\n  const id = parseInt(request.params.id, 10)\n\n  try {\n    const results = await pool.query('SELECT * FROM users WHERE id = $1', [id])\n    response.status(200).json(results.rows)\n  } catch (error) {\n    throw error\n  }\n}\n\nconst createUser = async (request, response) =&gt; {\n  const { name, email } = request.body\n\n  try {\n    const results = await pool.query(\n      'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',\n      [name, email]\n    )\n    response.status(201).send(`User added with ID: ${results.rows[0].id}`)\n  } catch (error) {\n    throw error\n  }\n}\n\nconst updateUser = async (request, response) =&gt; {\n  const id = parseInt(request.params.id, 10)\n  const { name, email } = request.body\n\n  try {\n    await pool.query('UPDATE users SET name = $1, email = $2 WHERE id = $3', [\n      name,\n      email,\n      id,\n    ])\n    response.status(200).send(`User modified with ID: ${id}`)\n  } catch (error) {\n    throw error\n  }\n}\n\nconst deleteUser = async (request, response) =&gt; {\n  const id = parseInt(request.params.id, 10)\n\n  try {\n    await pool.query('DELETE FROM users WHERE id = $1', [id])\n    response.status(200).send(`User deleted with ID: ${id}`)\n  } catch (error) {\n    throw error\n  }\n}\n\nexport {\n  getUsers,\n  getUserById,\n  createUser,\n  updateUser,\n  deleteUser,\n}<\/pre>\n<h2 id=\"setting-up-crud-functions-in-a-rest-api\">Setting up CRUD functions in a REST API<\/h2>\n<p>Now that we have all of our queries, we need to pull them into the <code>index.js<\/code> file and make endpoint routes for all the query functions we created.<\/p>\n<p>To get all the exported functions from <code>queries.js<\/code>, we\u2019ll import the module and assign it to a variable:<\/p>\n<pre class=\"language-javascript hljs\">import * as db from '.\/queries.js'<\/pre>\n<p>Now, for each endpoint, we\u2019ll set the HTTP request method, the endpoint URL path, and the relevant function:<\/p>\n<pre class=\"language-javascript hljs\">app.get('\/users', db.getUsers)\napp.get('\/users\/:id', db.getUserById)\napp.post('\/users', db.createUser)\napp.put('\/users\/:id', db.updateUser)\napp.delete('\/users\/:id', db.deleteUser)<\/pre>\n<p>Below is our complete <code>index.js<\/code> file, the entry point of the API server:<\/p>\n<pre class=\"language-javascript hljs\">import express from 'express'\nimport * as db from '.\/queries.js'\nconst app = express()\nconst port = 3000\n\napp.use(express.json())\napp.use(\n  express.urlencoded({\n    extended: true,\n  })\n)\n\napp.get('\/', (request, response) =&gt; {\n  response.json({ info: 'Node.js, Express, and Postgres API' })\n})\n\napp.get('\/users', db.getUsers)\napp.get('\/users\/:id', db.getUserById)\napp.post('\/users', db.createUser)\napp.put('\/users\/:id', db.updateUser)\napp.delete('\/users\/:id', db.deleteUser)\n\napp.listen(port, () =&gt; {\n  console.log(`App running on port ${port}.`)\n})<\/pre>\n<p>With just these two files, we have a server, database, and our API all set up. You can start up the server by hitting <code>index.js<\/code> again:<\/p>\n<pre class=\"language-bash hljs\">node index.js\nApp running on port 3000.<\/pre>\n<p>Now, if you go to <code>http:\/\/localhost:3000\/users<\/code> or <code>http:\/\/localhost:3000\/users\/1<\/code>, you\u2019ll see the JSON response of the two <code>GET<\/code> requests.<\/p>\n<p>To test our <code>POST<\/code>, <code>PUT<\/code>, and <code>DELETE<\/code> requests, we can use a tool like Postman or a VS Code extension like <a href=\"https:\/\/www.thunderclient.com\">Thunder Client<\/a> to send the HTTP requests. You can also use <a href=\"https:\/\/blog.logrocket.com\/an-intro-to-curl-the-basics-of-the-transfer-tool\/\">curl<\/a>, a command-line tool that is already available on your terminal.<\/p>\n<p>Using a tool like Postman or Thunder Client makes it simple to query endpoints with different HTTP methods. Simply enter your URL, choose the specific HTTP method, insert the JSON value if the endpoint is a PUT or POST route, and hit Send:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Add-new-user-Thunder-Client-VS-Code.png\" alt=\"Example Of How To Add A New User With Thunder Client And Vs Code\"><\/p>\n<p>The example above shows sending a <code>POST<\/code> request to the specified route. The <code>POST<\/code> option suggests that it is a <code>POST<\/code> request. The URL beside the method is the API endpoint, and the JSON content is the data to be sent to the endpoint. You can hit the different routes similarly.<br>\nHere\u2019s an example of sending a <code>POST<\/code> request to the specified route to create a new user using Postman:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Add-new-user-Postman.png\" alt=\"Example Of How To Add A New User Using Postman\"><\/p>\n<p>Here\u2019s an example of sending a <code>PUT<\/code> request to the specified route to modify a user by its ID:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Update-user-by-ID-Postman.png\" alt=\"Example Of How To Update User By Id Using Postman\"><\/p>\n<p>Here\u2019s an example of sending a <code>GET<\/code> request to the specified route to retrieve a user by its ID:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Read-user-by-ID-Postman.png\" alt=\"Example Of How To Read User By Id Using Postman\"><\/p>\n<p>Here\u2019s an example of sending a <code>GET<\/code> request to the specified route to retrieve all users:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Fetch-all-users-Postman.png\" alt=\"Example Of How To Fetch All Users By Id Using Postman\"><\/p>\n<p>Finally, here\u2019s an example of sending a <code>DELETE<\/code> request to the specified route to delete a user by its ID:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/Delete-user-Postman.png\" alt=\"Example Of How To Delete Users By Id Using Postman\"><\/p>\n<h2 id=\"solutions-to-common-issues-encountered-while-developing-apis\">Solutions to common issues encountered while developing APIs<\/h2>\n<p>Developing APIs can come with various challenges. Let\u2019s go over the solutions to two common issues encountered during API development: CORS issues and unhandled errors due to middleware order.<\/p>\n<h3 id=\"handling-cors-issues\">Handling CORS issues<\/h3>\n<p>Browser security policies can block requests from different origins. To address this issue, use the <code>cors<\/code> middleware in Express to handle cross-origin resource sharing (CORS).<br>\nRun the following command to install <code>cors<\/code>:<\/p>\n<pre class=\"language-bash hljs\">npm install cors<\/pre>\n<p>To use it, do the following:<\/p>\n<pre class=\"language-javascript hljs\">import express from 'express'\nimport cors from 'cors'\nconst app = express()\n\napp.use(cors())<\/pre>\n<p>This will enable CORS for all origins.<\/p>\n<h3 id=\"middleware-order-and-error-handling\">Middleware order and error handling<\/h3>\n<p>Middleware order can affect error handling, leading to unhandled errors. To address this issue, place error-handling middleware at the end of your middleware stack and use <code>next(err)<\/code> to pass errors to the error-handling middleware:<\/p>\n<pre class=\"language-javascript hljs\">app.use((req, res, next) =&gt; {\n    const error = new Error('Something went wrong');\n    next(error);\n});\n\/\/ Error-handling Middleware\napp.use((err, req, res, next) =&gt; {\n    console.error('Error:', err.message);\n    res.status(500).send('Internal Server Error');\n});<\/pre>\n<h2 id=\"securing-the-api\">Securing the API<\/h2>\n<p>When it comes to securing APIs, we need to implement various mechanisms to ensure the confidentiality, and integrity of the application and its data. Let\u2019s go over a few of these mechanisms now.<\/p>\n<h3 id=\"authentication\">Authentication<\/h3>\n<p>You can implement strong authentication mechanisms, such as JSON Web Tokens (JWT) or OAuth, to verify the identity of clients. Ensure that only authenticated and authorized users can access certain routes \u2014 in our case, the <code>POST<\/code>, <code>PUT<\/code>, and <code>DELETE<\/code> methods.<\/p>\n<p>I recommend the<a href=\"https:\/\/blog.logrocket.com\/using-passport-authentication-node-js\/\"> Passport middleware<\/a> for Node.js, which makes it easy to implement authentication and authorization. Passport v0.7 supports promise-based verify functions, so you can use <code>async<\/code>\/<code>await<\/code> directly. Here\u2019s an example:<\/p>\n<pre class=\"language-javascript hljs\">import passport from 'passport';\nimport { Strategy as LocalStrategy } from 'passport-local';\n\npassport.use(new LocalStrategy(async (username, password) =&gt; {\n  \/\/ Verify username and password\n  const user = await findUserByCredentials(username, password);\n  if (!user) {\n    throw new Error('Invalid credentials');\n  }\n  return user;\n}));<\/pre>\n<h3 id=\"authorization\">Authorization<\/h3>\n<p>It\u2019s important to enforce proper access controls to restrict access to specific routes or resources based on the user\u2019s role or permissions. For example, you can check if the user making a request has <code>admin<\/code> privileges before allowing or denying them permission to proceed with the request:<\/p>\n<pre class=\"language-javascript hljs\">function isAdmin(req, res, next) {\n    if (req.user &amp;&amp; req.user.role === 'admin') {\n        return next();\n    } else {\n        return res.status(403).json({ message: 'Permission denied' });\n    }\n}<\/pre>\n<p>You can apply the <code>isAdmin<\/code> middleware defined above to any protected routes, thus restricting access to those routes.<\/p>\n<h3 id=\"input-validation\">Input validation<\/h3>\n<p>Validate and sanitize user inputs to prevent SQL injection, XSS, and other security vulnerabilities. For example:<\/p>\n<pre class=\"language-javascript hljs\">import { body, validationResult } from 'express-validator';\n\napp.post('\/users', [\n    \/\/ add validation rules\n], (req, res) =&gt; {\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) {\n        return res.status(422).json({ errors: errors.array() });\n    }\n    \/\/ Process the request\n});<\/pre>\n<p>The code above allows you to specify validation rules for POST requests to the <code>\/users<\/code> endpoint. If the validation fails, it sends a response with the validation errors. If the incoming data is correct and safe, it proceeds with processing the request.<\/p>\n<h3 id=\"helmet-middleware\">Helmet middleware<\/h3>\n<p>You can use the <a href=\"https:\/\/blog.logrocket.com\/using-helmet-node-js-secure-application\/\">Helmet middleware<\/a> to set various HTTP headers for enhanced security:<\/p>\n<pre class=\"language-javascript hljs\">import helmet from 'helmet';\napp.use(helmet());<\/pre>\n<p>Configuring HTTP headers with Helmet helps protect your app from security issues like XSS attacks, CSP vulnerabilities, and more.<\/p>\n<h3 id=\"rate-limiting\">Rate limiting<\/h3>\n<p>Use <code>express-rate-limit<\/code> to reduce brute-force and abuse attempts against your API:<\/p>\n<pre class=\"language-javascript hljs\">import rateLimit from 'express-rate-limit';\n\nconst apiLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, \/\/ 15 minutes\n  max: 100, \/\/ limit each IP to 100 requests per window\n  standardHeaders: true,\n  legacyHeaders: false,\n});\n\napp.use('\/users', apiLimiter);<\/pre>\n<p>You can apply stricter limits to sensitive routes, such as authentication endpoints.<\/p>\n<h2 id=\"additional-notes-and-suggestions\">Additional notes and suggestions<\/h2>\n<p>You can build on this tutorial by implementing the following suggestions:<\/p>\n<ul>\n<li><strong>Integration with frontend frameworks \u2013<\/strong>&nbsp;Choose a frontend framework or library (e.g., React, Angular, Vue.js) to build a user interface for your application. Then, implement API calls from the frontend to interact with the backend CRUD operations. You can consider state management solutions (e.g., Redux, Vuex) for managing the state of your frontend application<\/li>\n<li><strong>Containerizing the API \u2013<\/strong>&nbsp;Write a Dockerfile to define the environment and dependencies needed to run your Node.js app. Create a <code>docker-compose.yml<\/code> file for managing multiple containers, such as the Node.js app and PostgreSQL database. This will make your Node.js application easier to deploy and set up on other machines<\/li>\n<li><strong>Migrating to TypeScript \u2013<\/strong>&nbsp;Add TypeScript for stronger type safety across your route handlers, request payloads, and database responses. This can make larger API codebases easier to maintain and refactor over time<\/li>\n<li><strong>Adding API documentation with OpenAPI \u2013<\/strong>&nbsp;Document your endpoints with an OpenAPI spec and expose interactive docs with tools like <code>swagger-ui-express<\/code> so consumers can quickly test and understand your API<\/li>\n<li><strong>Evaluating Prisma as an ORM alternative \u2013<\/strong>&nbsp;If you prefer a schema-first workflow and generated type-safe queries, Prisma is a modern alternative to writing raw SQL in every route<\/li>\n<li><strong>Implementing unit\/integration tests \u2013<\/strong>&nbsp;Write unit tests for individual functions and components of your application to ensure that they work as expected. Use testing frameworks like Mocha, Jest, or Vitest for writing and running tests. Implement integration tests to verify the interactions between different components in your front-end application and the overall functionality of your API<\/li>\n<li><strong>Continuous integration\/deployment (CI\/CD) \u2013<\/strong>&nbsp;Set up CI\/CD pipelines to automate the testing and deployment processes. Use tools like Jenkins, Travis CI, or GitHub Actions to streamline the development\/deployment workflow<\/li>\n<\/ul>\n<p>While actually implementing these next steps is beyond the scope of this tutorial, you can use these ideas to apply what we\u2019ve discussed to a real use case.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>You now have a working API server running on Node.js, connected to a PostgreSQL database, and capable of handling full CRUD operations.<br>\nAlong the way, you set up PostgreSQL from the command line, created a database and schema, and built an Express server that maps HTTP methods to SQL queries using the <code>pg<\/code> client.<br>\nFrom here, this setup can serve as a foundation for real applications. You can layer in authentication, validation, and a frontend, or evolve it into a more structured backend as your project grows.<\/p>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Build a CRUD REST API with Node.js, Express, and PostgreSQL, then modernize it with ES modules, async\/await, built-in Express middleware, and safer config handling.<\/p>\n","protected":false},"author":156415287,"featured_media":185856,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2147999,1],"tags":[2109699,2109737],"class_list":["post-318","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev","category-uncategorized","tag-node","tag-postgresql"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.1.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>CRUD REST API with Node.js, Express, and PostgreSQL - LogRocket Blog<\/title>\n<meta name=\"description\" content=\"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"CRUD REST API with Node.js, Express, and PostgreSQL - LogRocket Blog\" \/>\n<meta property=\"og:description\" content=\"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/\" \/>\n<meta property=\"og:site_name\" content=\"LogRocket Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-25T16:00:49+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-27T14:41:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png\" \/>\n\t<meta property=\"og:image:width\" content=\"895\" \/>\n\t<meta property=\"og:image:height\" content=\"597\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Tania Rascia\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Tania Rascia\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"16 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/\",\"url\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/\",\"name\":\"CRUD REST API with Node.js, Express, and PostgreSQL - LogRocket Blog\",\"isPartOf\":{\"@id\":\"https:\/\/blog.logrocket.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png\",\"datePublished\":\"2026-03-25T16:00:49+00:00\",\"dateModified\":\"2026-03-27T14:41:40+00:00\",\"author\":{\"@id\":\"https:\/\/blog.logrocket.com\/#\/schema\/person\/2f78bd7d34dc3cc8a6372c06e125aac6\"},\"description\":\"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#primaryimage\",\"url\":\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png\",\"contentUrl\":\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png\",\"width\":895,\"height\":597,\"caption\":\"Crud Rest Api With Node Js Express And Postgresql\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.logrocket.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"CRUD REST API with Node.js, Express, and PostgreSQL\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.logrocket.com\/#website\",\"url\":\"https:\/\/blog.logrocket.com\/\",\"name\":\"LogRocket Blog\",\"description\":\"Resources to Help Product Teams Ship Amazing Digital Experiences\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.logrocket.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.logrocket.com\/#\/schema\/person\/2f78bd7d34dc3cc8a6372c06e125aac6\",\"name\":\"Tania Rascia\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.logrocket.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e1955b5140137945e70760f3aee62a00b14c117118c298b81553b1016ed3d390?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e1955b5140137945e70760f3aee62a00b14c117118c298b81553b1016ed3d390?s=96&d=mm&r=g\",\"caption\":\"Tania Rascia\"},\"description\":\"Software developer, writer, maker of things. Find me online at tania.dev.\",\"sameAs\":[\"http:\/\/tania.dev\"],\"url\":\"https:\/\/blog.logrocket.com\/author\/taniarascia\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"CRUD REST API with Node.js, Express, and PostgreSQL - LogRocket Blog","description":"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/","og_locale":"en_US","og_type":"article","og_title":"CRUD REST API with Node.js, Express, and PostgreSQL - LogRocket Blog","og_description":"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.","og_url":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/","og_site_name":"LogRocket Blog","article_published_time":"2026-03-25T16:00:49+00:00","article_modified_time":"2026-03-27T14:41:40+00:00","og_image":[{"width":895,"height":597,"url":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png","type":"image\/png"}],"author":"Tania Rascia","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Tania Rascia","Est. reading time":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/","url":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/","name":"CRUD REST API with Node.js, Express, and PostgreSQL - LogRocket Blog","isPartOf":{"@id":"https:\/\/blog.logrocket.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#primaryimage"},"image":{"@id":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png","datePublished":"2026-03-25T16:00:49+00:00","dateModified":"2026-03-27T14:41:40+00:00","author":{"@id":"https:\/\/blog.logrocket.com\/#\/schema\/person\/2f78bd7d34dc3cc8a6372c06e125aac6"},"description":"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.","breadcrumb":{"@id":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#primaryimage","url":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png","contentUrl":"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/05\/CRUD-REST-API-Node-js-Express-PostgreSQL.png","width":895,"height":597,"caption":"Crud Rest Api With Node Js Express And Postgresql"},{"@type":"BreadcrumbList","@id":"https:\/\/blog.logrocket.com\/crud-rest-api-node-js-express-postgresql\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.logrocket.com\/"},{"@type":"ListItem","position":2,"name":"CRUD REST API with Node.js, Express, and PostgreSQL"}]},{"@type":"WebSite","@id":"https:\/\/blog.logrocket.com\/#website","url":"https:\/\/blog.logrocket.com\/","name":"LogRocket Blog","description":"Resources to Help Product Teams Ship Amazing Digital Experiences","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.logrocket.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.logrocket.com\/#\/schema\/person\/2f78bd7d34dc3cc8a6372c06e125aac6","name":"Tania Rascia","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.logrocket.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e1955b5140137945e70760f3aee62a00b14c117118c298b81553b1016ed3d390?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e1955b5140137945e70760f3aee62a00b14c117118c298b81553b1016ed3d390?s=96&d=mm&r=g","caption":"Tania Rascia"},"description":"Software developer, writer, maker of things. Find me online at tania.dev.","sameAs":["http:\/\/tania.dev"],"url":"https:\/\/blog.logrocket.com\/author\/taniarascia\/"}]}},"yoast_description":"Learn how to build a CRUD REST API with Node.js, Express, and PostgreSQL using modern practices like ES modules, async\/await, environment variables, and built-in middleware.","_links":{"self":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts\/318","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/users\/156415287"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/comments?post=318"}],"version-history":[{"count":72,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts\/318\/revisions"}],"predecessor-version":[{"id":212599,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/posts\/318\/revisions\/212599"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/media\/185856"}],"wp:attachment":[{"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/media?parent=318"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/categories?post=318"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.logrocket.com\/wp-json\/wp\/v2\/tags?post=318"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}