Quickly build an API in Node.js using async/await & PostgreSQL


2017-05-15 · 6 min read

The JavaScript ecosystem is vast and rich. There is a lot of possibilities to explore and a lot of choices to make. NPM itself has now more than 500,000 packages available which makes it the largest package repository in the world. All that may overwhelm and distract us from the task at hand, and as a result hinder our productivity. If you are tired of making various modules compatible one with another, but rather looking for guidance and/or conventions while building modern web application in JavaScript, let me introduce to you Huncwot.

Huncwot is an opinionated Node.js web framework built for ESNext era providing a coherent solution to build modern, full-stack JavaScript web applications. It is being built with battery included approach in mind, i.e. it comes with a (eventually large) library of useful modules which are developed in a coherent way. This stands in direct opposition to Express or Koa approach. Huncwot tries to formalize conventions and eliminate valueless choices by providing solid defaults for building web applications that increase the programmers productivity.

Huncwot exclusively supports Node 7.6+ to avoid a transpilation pipeline of any kind (there are, however, plans to support TypeScript by default). It is an integrated solution that optimizes for programmers productivity by reducing choices and incorporating community conventions. The framework encourages the use of async/await syntax which makes the code easier to test, debug, read and reason about.

In this article of the series, we are going start with the backend side by building a simple RESTful API for a (yet another) « To Do » application to demonstrate what Huncwot brings to the table. In the follow-up article, we will complement that application with a UI layer being built using a (modern) component-based approach.

Getting started

Let's start by installing the framework

npm install -g huncwot

Next, we need to generate a new Huncwot application by running huncwot new command. Huncwot uses yarn to install its dependencies, so be sure to have it installed as well.

huncwot new todoapp
cd todoapp

You can immediately run that application by using huncwot server (or huncwot s for short) command

huncwot server

Routes & corresponding handlers

Huncwot enforces some conventions that aim to increase your productivity. One of those conventions is related to building RESTFul APIs: each file inside controllers/ directory is considered to be a REST route for a specific resource and it is supposed to define five functions to operate on it i.e. Browse, Read, Edit, Add, Delete in short BREAD, which is a kind of (tastier) extension of CRUD approach. Each of those functions is triggered by a corresponding HTTP method i.e. Browse and Read by GET, Edit by PUT, Add by POST and finally Delete by DELETE.

In contrast to Express or Koa, Huncwot handlers only take an HTTP request as input and always return an HTTP response - this approach is inspired by Clojure's ring web library. ok & created are convenience wrappers around HTTP statuses i.e. ok corresponds to 200 OK while created to 201 Created.

const { ok, created } = require('huncwot/response');
const db = require('huncwot/db');
 
async function browse(request) {
  const results = await db('tasks');
  return ok(results);
}
 
async function read(request) {
  const { id } = request.params;
  const result = await db('tasks').where({ id });
  return ok(result);
}
 
async function edit(request) {
  const { id, name } = request.params;
  await db('tasks').where({ id }).update({ name });
  return ok({ status: `success: ${id} changed to ${name}` });
}
 
async function add(request) {
  const { name } = request.params;
  await db('tasks').insert({ name });
  return created({ status: `success: ${name} created` });
}
 
async function destroy(request) {
  const { id } = request.params;
  await db('tasks').where({ id }).del();
  return ok({ status: `success: ${id} destroyed` });
}
 
module.exports = { browse, read, edit, add, destroy }

Persistence layer

This automatically generated controller looks great, but how Huncwot will know which database to use? The only missing part is to set up our database using huncwot db setup. By default, Huncwot generates a configuration for SQLite RDMS:

{
  "client": "sqlite3",
  "development": {
    "filename": "./db/development.sqlite3"
  },
  "test": {
    "filename": "./db/test.sqlite3"
  },
  "production": {
    "filename": "./db/production.sqlite3"
  }
}

PostgreSQL integration

If you fancy SQLite, you just need to setup the database (skip below). For the purpouse of this article we've decided, however, to integrate with PostgreSQL RDMS. Adapting the configuration is relatively straigtforward. I'm connecting here to a database called todo_dev accessible by a username (PostgreSQL role) zaiste with no password associated:

{
  "client": "postgresql",
  "development": {
    "database": "todo_dev",
    "username": "zaiste",
    "password": ""
  }
  ...
}

You can check if the credentials properly work with PostgreSQL server using PostgreSQL's psql [database [username]] CLI utility tool. In my case it would be:

psql todo_dev zaiste

Let's finish it up with setting up our database. Huncowt automatically generates a basic example of a database schema, it creates an example table and populates it with some dummy data using a regular SQL script available at db/tasks.sql

DROP DATABASE IF EXISTS todo_dev;
CREATE DATABASE todo_dev;
 
\c todo_dev;
 
CREATE TABLE tasks (
  ID SERIAL PRIMARY KEY,
  name VARCHAR,
  done BOOLEAN
);
 
INSERT INTO tasks (name, done)
VALUES
    ('Share the love about Huncwot', false),
    ('Build a fantastic web application', false),
    ('Give back to the community', false);

All this is meant as a starting point and to show you how all the pieces are combined together; eventually all those files are supposed to be replaced.

Testing routes

Once you start the application using huncwot s, you can start playing with that resource at /tasks. I will be using httpie instead of curl to issue HTTP requests in the examples below (be sure to have it installed before following along).

Let's start by retrieving all tasks

http :5544/tasks

Next, let's retrieve a single task by id

http :5544/tasks/2

Adding a new task is as simple as

http POST :5544/tasks name="Task X"

You can verify if the task has been added to the list by getting all of them

http :5544/tasks

Editing a task in our example is almost identical to adding a new task

http PUT :5544/tasks/2 name="Task 22"

Finally, let's delete a task

http DELETE :5544/tasks/2

We have now a fully working RESTful API integrated with PostgreSQL RDMS. Huncwot tries to keep new abstractions to minimum e.g. you interact with the database using plain, old SQL queries.

Stay tuned for the next article which will present how to build a componend-based UI on top of this RESTful API using Marko as UI library and MobX for unified approach to state management.