Diogo Souza Brazilian dev. Creator of altaluna.com.br

Creating REST APIs with Deno and Postgres

11 min read 3258

building rest API with deno and postgres tutorial feature image

Editor’s Note: This post was updated on 4 May 2021 to incorporate updated code and information on Deno version 1.9.2.

Created by the minds behind Node.js, Deno is similarly gaining traction among developers. After maturing and evolving features that Node failed to deliver such as security, modules, and dependencies, Deno is proving to be as powerful as its predecessor.

It’s a TypeScript runtime built on top of the robust Google V8 Engine. But don’t worry, Deno also supports vanilla JavaScript, which is what we’ll be using in this article.

What is Deno?

Deno was created under a few conditions: First off, it’s secure, meaning that its default execution is based in a sandbox environment.

There’s no access from runtime to things like network, file system, etc. When your code tries to access these resources, you’re prompted to allow the action.

It loads modules by URLs (like the browsers). This allows you to use decentralized code as modules and import them directly into your source code without having to worry about registry centers. It’s also browser-compatible. For example, if you’re using ES modules, you don’t have to worry about the usage of Webpack or Gulp.

Additionally, it’s TypeScript-based. If you already work with TypeScript, it’s perfect for you: very straightforward, and there’s no need for extra settings. If you don’t work with TypeScript, that’s no problem. You can also use it with plain JavaScript.

You can read more about it here and in its official documentation. In this article, we’re going to focus more on the how-to.

Specifically, we’ll go over how to create a REST API from scratch using only JavaScript, Deno, and a connection to a Postgres database.

The application we’ll develop is a basic CRUD over a domain of beers.

We made a custom demo for .
No really. Click here to check it out.

Setting up the Deno project

First, you need to have the tools and everything set up. For this article, you’ll need:

  • An IDE of your choice — we’ll be using VS Code
  • A Postgres server and your favorite GUI tool to manage it
  • Deno

Installing Deno is very simple. However, because Deno is constantly getting updated, we’ll focus this article on version 1.9.2, so it’ll always work no matter what new features are released.

For this, run the following command:

// With Shell
curl -fsSL https://deno.land/x/install/install.sh | sh -s v1.9.2
// With PowerShell
$v="1.9.2"; iwr https://deno.land/x/install/install.ps1 -useb | iex

Then, run the command deno --version to check if the installation worked.

You should see something like this:

deno 1.9.2 (release, x86_64-apple-darwin)
typescript 4.2.2

Deno project structure

Next, let’s create the project structure, including initial files and folders. Inside a folder of your preference, create the same structure as is seen in the image below:

Rest API Deno Postgres file structure

Checking for Deno version

The structure can be described as follows:

controllers: hold the JS files that will handle the requests arriving, the further calls to the services and below layers and, finally, the delivery of the responses. All of those objects are inherited from Deno, so you don’t need to worry about whether you’ll need to handle requests/responses manually.

db: the folder hosting our SQL script of creation and the direct connection to our Postgres database.

repositories: these JS files will handle the management of the database operations. Each create, delete, or update will take place, in its logic, here.

services: these are the files that will handle the business logic of our operations, such as validations, transformations over the data, etc.

The application

Let’s start with the code of our first and most important file, index.js.

Take a look at the following code:

import { Application } from "https://deno.land/x/oak/mod.ts";
import { APP_HOST, APP_PORT } from "./config.js";
import router from "./routes.js";
import _404 from "./controllers/404.js";
import errorHandler from "./controllers/errorHandler.js";

const app = new Application();


console.log(`Listening on port:${APP_PORT}...`);

await app.listen(`${APP_HOST}:${APP_PORT}`);

We need a web framework to deal with the details of the request and response handling, thread management, errors, etc. For Node, it’s common to use Express or Koa for this purpose.

However, as we’ve seen, Deno doesn’t support Node libraries.

We need to use another one inspired in Koa, the Oak: a middleware framework for Deno’s net server. Note that we’re using the version 4.0.0 of Oak. This is the right version for Deno 1.0.0. When you don’t provide a version, Deno will always fetch the latest, which can have breaking changes and mess up with your project. So be careful!

It has a middleware framework inspired by Koa, and its middleware router was inspired by koa-router.

Its usage is very similar to Express, as you can see by the code listing. In the first line, we’re importing the TS module directly from the deno.land URL.

The rest of the imports will be configured further.

The Application class is where everything starts with Oak.

We instantiate it and add the error handler, the controllers, the routing system and, ultimately, call the method listen() to start the server passing the URL (host + port).

Here you can see the code for config.js (place it in the root of the project):

export const APP_HOST = Deno.env.get("APP_HOST") || "";
export const APP_PORT = Deno.env.get("APP_PORT") || 4000;

Very familiar so far, isn’t it? Let’s go to the routing now.

Like with Express, we need to establish the routers that will redirect our requests to the proper JavaScript functions that, in turn, will handle them, store, or search for data and return the results.

Take a look at the code for routes.js (also in the root folder):

import { Router } from "https://deno.land/x/oak/mod.ts";;
import getBeers from "./controllers/getBeers.js";
import getBeerDetails from "./controllers/getBeerDetails.js";
import createBeer from "./controllers/createBeer.js";
import updateBeer from "./controllers/updateBeer.js";
import deleteBeer from "./controllers/deleteBeer.js";

const router = new Router();

  .get("/beers", getBeers)
  .get("/beers/:id", getBeerDetails)
  .post("/beers", createBeer)
  .put("/beers/:id", updateBeer)
  .delete("/beers/:id", deleteBeer);

export default router;

So far, nothing should be working yet. Don’t worry — we still need to configure the rest of the project before starting it up.

This last listing shows that Oak will also take care of the routing system for us.

The Router class, more specifically, will be instantiated to allow the use of the correspondent methods for each HTTP GET, POST, PUT, and DELETE operation.

The imports at the beginning of the file correspond to each of the functions that will handle the respective request.

You can decide whether you prefer it this way, or if you’d rather have everything in the same controller file.

Database and repository in Deno and Postgres

Before we proceed with more JavaScript code, we need to set up the database.

Make sure you have the Postgres server installed and running at your localhost. Connect to it, and create a new database called logrocket_deno.

Then, enter it. In the public schema, run the following create script:

    name VARCHAR(50) NOT NULL,
    brand VARCHAR(50) NOT NULL,
    is_premium BOOLEAN,
    registration_date TIMESTAMP

This script is also available at the /db folder of my version of the project. It creates a new table, “beers,” to store the values of our CRUD.

Note that the primary key is auto-incremented (via SERIAL keyword) to facilitate our job with the ID generation strategy. Now, let’s create the file that will handle the connection to Postgres.

In the db folder, create the database.js file and add the following content:

import { Client } from "https://deno.land/x/postgres/mod.ts";

class Database {
  constructor() {

  async connect() {
   this.client = new Client({
      user: "postgres",
      database: "logrocket_deno",
      hostname: "",
      password: "postgres",
      port: 5432

    await this.client.connect();

export default new Database().client;

Make sure to adjust the connection settings according to your Postgres configurations. The config is pretty simple.

Deno has created its deno-postgres (PostgreSQL driver for Deno) based on node-postgres and pg. If you’re a Node user, you’re going to be familiar with the syntax. Just be aware that the settings slightly change depending on the database you use.

Here, we’re passing the setting object as a Client parameter. In MySQL, however, it goes directly into the connect() function.

Inside the repositories folder, we’re going to create the file beerRepo.js, which will host the repositories to access the database through the file we’ve created above.

This is its code:

import client from "../db/database.js";

class BeerRepo {
  create(beer) {
    return client.queryObject(
      "INSERT INTO beers (name, brand, is_premium, registration_date) VALUES ($1, $2, $3, $4)",

  selectAll() {
    return client.queryArray("SELECT * FROM beers ORDER BY id");

  selectById(id) {
    return client.queryObject(`SELECT * FROM beers WHERE id = $1`, id);

  update(id, beer) {
    var latestBeer = this.selectById(id);
    var query = `UPDATE beers SET name = $1, brand = $2, is_premium = $3 WHERE id = $4`;

    return client.queryObject(
      beer.name !== undefined ? beer.name : latestBeer.name,
      beer.brand !== undefined ? beer.brand : latestBeer.brand,
      beer.is_premium !== undefined ? beer.is_premium : latestBeer.is_premium,

  delete(id) {
    return client.queryObject(`DELETE FROM beers WHERE id = $1`, id);

export default new BeerRepo();

Import the database.js file that connects to the database. Then, the rest of the file is just database-like CRUD operations.

In order to prevent SQL injection — like every other major database framework — Deno allows us to pass parameters to our SQL queries as well. Again, each database has its own syntax. With Postgres, for example, we use the dollar sign followed by the number of the param in its specific order.

The order here is very important. In MySQL, the operator is a question mark (?).

The values of each param come after, as a varargs param (in Postgres: for MySQL, it would be an array).

Each item must be in the exact same position as its corresponding query operator. The query() function is the one we’ll use every time we want to access or alter data in the database. We’ll also pay special attention to our update method.

That’s one of many possible strategies you can use to update only what the user is passing.
Here, we’re first fetching the user by the ID and storing it into a local variable. Then, when checking if each beer attribute is within the request payload we can decide whether to use it or the stored value.

Creating the services layer

Our repository is set.

Now, let’s move on to the services layer.

Inside of the services folder, create the file beerService.js and add in the following code:

import beerRepo from "../repositories/beerRepo.js";

export const getBeers = async () => {
  const beers = await beerRepo.selectAll();

  var result = new Array();

  beers.rows.map((beer) => {
    var obj = new Object();

    beers.rowDescription.columns.map((el, i) => {
      obj[el.name] = beer[i];

  return result;

export const getBeer = async (beerId) => {
  const beers = await beerRepo.selectById(beerId);

  var result = new Object();
  beers.rows.map((beer) => {
    result = beer;

  return result;

export const createBeer = async (beerData) => {
  const newBeer = {
    name: String(beerData.name),
    brand: String(beerData.brand),
    is_premium: "is_premium" in beerData ? Boolean(beerData.is_premium) : false,
    registration_date: new Date(),

  await beerRepo.create(newBeer);

  return newBeer.id;

export const updateBeer = async (beerId, beerData) => {
  const beer = await getBeer(beerId);

  if (Object.keys(beer).length === 0 && beer.constructor === Object) {
    throw new Error("Beer not found");

  const updatedBeer = {
    name: beerData.name !== undefined ? String(beerData.name) : beer.name,
    brand: beerData.brand !== undefined ? String(beerData.brand) : beer.brand,
      beerData.is_premium !== undefined
        ? Boolean(beerData.is_premium)
        : beer.is_premium,

  beerRepo.update(beerId, updatedBeer);

export const deleteBeer = async (beerId) => {

This is one of the most important files we have. It’s here where we interface with the repository and receive calls from the controllers.

Each method also corresponds to one of the CRUD operations and, because the Deno database nature is inherently asynchronous, it always returns a promise. This is why we need to await until it finishes in our synchronous code.

Plus, the return is an object that does not correspond to our exact business object Beer, so we have to transform it into an understandable JSON object.getBeers will always return an array, and getBeer will always return a single object.

The structure of both functions is very similar.

The beers result is an array of arrays because it encapsulates a list of possible returns for our query, and each return is an array as well (given that each column value comes within this array). rowDescription, in turn, stores the information (including the names) of each column the results have.

Some other features, like validations, also take place here. In the updateBeer function, you can see that we’re always checking if the given beerId in fact exists in the database before proceeding with updating. Otherwise, an error will be thrown. Feel free to add whichever validations or additional code you want.

Creating controllers

Now it’s time to create the handlers of our requests and responses.

Input and output validations better adhere to this layer. Let’s start with the error management files — the ones we’ve seen in the index.js.

In the controllers folder, create the files 404.js and errorHandler.js.

Code for 404.js:

export default ({ response }) => {
  response.status = 404;
  response.body = { msg: "Not Found" };

Code for errorHandler.js:

export default async ({ response }, nextFn) => {
  try {
    await nextFn();
  } catch (err) {
    response.status = 500;
    response.body = { msg: err.message };

They are very simple. In the first one, we’re just exporting a function that will take care of business exceptions whenever we throw them, like HTTP 404.

The second one will take care of any other type of unknown errors that may happen in the application lifecycle, treat them like HTTP 500, and send the error message in the response body.

Now, let’s get to the controllers. Let’s start with the getters.

This is the content for getBeers.js:

import { getBeers } from "../services/beerService.js";

export default async ({ response }) => {
  response.body = await getBeers();

Each controller operation must be async. Each controller operation receives either one or both request and response objects as parameters.

They’re intercepted by the Oak API and preprocessed before arriving at the controller or getting back to the client caller. Regardless of the type of logic you put in there, don’t forget to set the response body since it is the result of your request.

The following is the content for getBeerDetails.js:

import { getBeer } from "../services/beerService.js";

export default async ({ params, response }) => {
  const beerId = params.id;

  if (!beerId) {
    response.status = 400;
    response.body = { msg: "Invalid beer id" };

  const foundBeer = await getBeer(beerId);
  if (!foundBeer) {
    response.status = 404;
    response.body = { msg: `Beer with ID ${beerId} not found` };

  response.body = foundBeer;

This content is similar to our content for getBeers.js, except for the validations.

Because we’re receiving the beerId as a parameter, it’s good to check if it’s filled. If the value for that param doesn’t exist, send a corresponding message in the body. The next step is the creation file.

This is the content for the file createBeer.js:

import { createBeer } from "../services/beerService.js";

export default async ({ request, response }) => {
  if (!request.hasBody) {
    response.status = 400;
    response.body = { msg: "Invalid beer data" };

  const { name, brand, is_premium } = await request.body().value;

  console.log(await request.body({ type: "json" }).value);

  if (!name || !brand) {
    response.status = 422;
    response.body = { msg: "Incorrect beer data. Name and brand are required" };

  const beerId = await createBeer({ name, brand, is_premium });

  response.body = { msg: "Beer created", beerId };

Again, a few validations take place to guarantee that the input data is valid regarding required fields. Validations also confirm that a body comes with the request.

The call for the createBeer service function passes each argument individually. If the beer object increases in its number of attributes, it would not be wise to maintain such a function.

You can come up with a model object instead, which would store each one of your beer’s attributes and be passed around the controllers and service methods.

This is our updateBeer.js content:

import { updateBeer } from "../services/beerService.js";

export default async ({ params, request, response }) => {
  const beerId = params.id;

  if (!beerId) {
    response.status = 400;
    response.body = { msg: "Invalid beer id" };

  if (!request.hasBody) {
    response.status = 400;
    response.body = { msg: "Invalid beer data" };

  const { name, brand, is_premium } = await request.body().value;

  await updateBeer(beerId, { name, brand, is_premium });

  response.body = { msg: "Beer updated" };

As you can see, it has almost the same structure. The difference is in the params config. Because we’re not allowing every attribute of a beer to be updated, we limit which ones will go down to the service layer. The beerId must also be the first argument since we need to identify which database element to update.

And finally, the code for our deleteBeer.js:

import { deleteBeer, getBeer } from "../services/beerService.js";

export default async ({
}) => {
  const beerId = params.id;

  if (!beerId) {
    response.status = 400;
    response.body = { msg: "Invalid beer id" };

  const foundBeer = await getBeer(beerId);
  if (!foundBeer) {
    response.status = 404;
    response.body = { msg: `Beer with ID ${beerId} not found` };

  await deleteBeer(beerId);
  response.body = { msg: "Beer deleted" };

Note how similar it is to the others.

Again, if you feel it is too repetitive, you can mix these controller codes into one single controller file. That would allow you to have less code because the common code would be together in a function, for example.

Now let’s test it.

To run the Deno project, go to your prompt command line. In the root folder, issue the following command:

deno run --allow-net --allow-env index.js

Remember that Deno works with secure resources. So, it means that to allow HTTP calls and access env variables we need to explicitly ask for that. Those flags do the job, respectively.

The logs will show Deno downloading all the dependencies our project needs. The message “Listening on port:4000...” must appear.

To test the API, we’ll make use of the Postman utility tool. Feel free to use whichever one you prefer.

This is the example of a POST creation in action:

An example of the POST creation.

Creating a beer in our Deno API

After that, go to the GET operation to list all the beers in the database:

The GET operation used to list all of the beers in our database.
Listing all beers from database.


I’ll leave the rest of the operation tests to you. You can also go to the database and check directly from there for the values to be inserted, updated, or deleted. The final code for this tutorial can be found here.

Note that we’ve finished a complete functional CRUD-like API without having to make use of Node.js or a node_modules directory (because Deno maintains the dependencies in cache).

Every time you want to use a dependency, just state it through the code and Deno will take care of downloading it (there’s no need for a package.json file).

Also, if you want to use it with TypeScript, there’s no need to install it either. Just go ahead and start coding with TypeScript right away. Additionally, VS Code provides a very useful extension for Deno to help with autocompletion, module imports, formatting, and more.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Diogo Souza Brazilian dev. Creator of altaluna.com.br

10 Replies to “Creating REST APIs with Deno and Postgres”

  1. > In order to prevent SQL injection — like every other major database framework — Deno allows us to pass parameters to our SQL queries as well.

    Am I missing something or is the `beerRepo.update()` function *full* of SQL injections? Example:

    query += ` SET name = ‘${beer.name}’` …

  2. Yes, yes it is…. There’s no input sanitization whatsoever 🙈

  3. deno run –allow-net –allow-env index.js
    error: No such file or directory (os error 2)

    I got this error, when i am trying run

  4. Compile file:///home/zire/dcode/postgr/index.js
    error: Uncaught AssertionError: Unexpected skip of the emit.
    at Object.assert ($deno$/util.ts:33:11)
    at compile ($deno$/compiler.ts:1170:7)
    at tsCompilerOnMessage ($deno$/compiler.ts:1338:22)
    at workerMessageRecvCallback ($deno$/runtime_worker.ts:72:33)
    at file:///home/zire/dcode/postgr/__anonymous__:1:1

  5. Hi guys, thanks very much for the comments. I’ve already addressed all of them both in the article and in the GitHub repo.

    Please, let me know if there’s still any problems to you. 🙂

  6. Great tutorial thanks! Hey would love to see a Typescript version of this project, anyone have an example of PostgreSQL + Deno + Typescript?

  7. Hey folks, I’ve had a go at converting this project to Typescript. Made some minor improvements/changes along the way. Classes seem to cause problems when instantiating the database, “possible undefined value” errors, so I removed them as they aren’t really needed.
    For any of you who are Typescript Savvy, I would love to get some feedback if I’ve gone about this the right way?

Leave a Reply