Nodal: A Tutorial for Easily Creating API Services in Node.js

By  on  

If you haven't heard about Nodal yet, or you have but you're unsure of where to begin, this tutorial is for you! Make sure you keep up with Nodal on GitHub to follow project updates.

Nodal is a Node.js server platform and framework that enables you to develop API services easily. With products growing increasingly multi-platform (web, mobile, IoT) we need to start thinking about our backend architecture using a service-oriented approach to begin with instead of as an afterthought.

The goal of Nodal is to build an encyclopedia around Node.js that allows any developer — newbie or veteran, back-end or front-end — to join in and begin creating web applications effortlessly.

Nodal has built-in PostgreSQL support, makes frequent use of modern ES6 syntax and idioms, and encourages fairly rigid software design patterns. This allows Nodal to make a bunch of decisions for you so that you can get your applications built and deployed quickly, with more confidence. Get to writing the code that powers your product faster and let Nodal do the heavy lifting.

While Nodal's core competency is not being used as a traditional monolithic webserver, it can still be used for that purpose. It's out of scope of this tutorial, but by sniffing around documentation you'll be able to find out how to get Nodal to do whatever you'd like - serving a static branding website, template support, etc.

Our First Nodal Project

While a lot of Nodal will be familiar to you if you've worked with an MVC framework such as Django or Ruby on Rails before, we'll start with getting a basic API Server set up and generate a few models. It's good to start with a sample project so let's make a Twitter clone called Instatweet.

For reference, you can find a completed version of the project used for this tutorial at keithwhor/instatweet-api on GitHub.

Setting Up Nodal

In order to install Nodal and get it working with a database we'll need to do the following:

  1. Install Node.js 4.x or higher
  2. Install PostgreSQL
  3. Install Nodal

Installing Node.js

To make sure you're running a recent version of Node.js, just head over to Nodejs.org and download the most recent 4.x version or higher. Nodal has been developed explicitly for 4.x, so that's what's recommended at present time.

Installing PostgreSQL

If you're using Mac OS X I strongly recommend using Postgres.app to get PostgreSQL up and running on your machine. Make sure you configure your $PATH to get access to the command line tools. Once Postgres.app is installed and you've followed the instructions to set up the CLI, make sure there's a postgres superuser named postgres (no password) with:

$ createuser postgres -s

For a Windows installation, you can check out the PostgreSQL website.

Installing Nodal

You're almost ready to start setting up API servers in a flash. :)

To install Nodal, simply open up your Terminal command line and type:

$ npm install nodal -g

This will install the Nodal command line tools and the current version of the Nodal core. You're all ready to begin!

Creating Your Project

Project setup is easy. Go to the directory in which you'd like to create your project folder and type:

$ nodal new

You'll see a prompt...

Welcome to Nodal! v0.7.x
? Name (my-nodal-project)

You can name it whatever you'd like, but I'll be using instatweet-api for this tutorial. You'll also be asked to enter in your name. Nodal will create your project directory for you, and copy all necessary packages (node_modules) from your global installation of nodal.

Starting Your Server

Boot your server with:

$ nodal s

You'll see something like:

[Nodal.Daemon] Startup: Initializing
Initializer Ready
[Nodal.Daemon] Startup: Spawning HTTP Workers
[Nodal.27454] Startup: Starting HTTP Worker
[Nodal.27455] Startup: Starting HTTP Worker
[Nodal.27455] Ready: HTTP Worker listening on port 3000
[Nodal.27454] Ready: HTTP Worker listening on port 3000

In fact, you'll see a new [Nodal.XXXX] process spawned for each one of the cores on your processor. So if you see 16 messages here (2 for each core), don't worry about it. That's just the Daemon doing its job. :)

Nodal does a good job of rebooting itself on file changes, so just leave the server running and open another Terminal window and navigate back to your instatweet-api project directory.

Creating your first Model

Creating a Model is simple. Fire up your terminal and type:

$ nodal g:model Tweet user_id:int body:string

You'll see something like:

Create: ./app/models/tweet.js
Create: ./db/migrations/2016022003113671__create_tweet.js

A Model file and a Migration have automatically been created for you.

The Model file contains information about the Tweet object in your project, which contains an integer field called user_id and a body which is a string.

The Migration file is a set of commands for creating a table in the Postgres database to hold Tweet data, with the fields as columns in the table.

Creating your first Controller

Now that we have a Model, we want a Controller for that model. We can use the command line again to make this easy for us:

$ nodal g:controller v1 --for:Tweet

The v1 tells us a namespace and the --for:Tweet tells us the Controller is a Create-Read-Update-Destroy (CRUD) controller for a Model resource (will do some things for us). Note that something like:

$ nodal g:controller v1/Tweet

is also acceptable, but it will create an empty Controller template without the CRUD commands, so you'll need to write all the functions yourself.

From this command you should see:

Create: ./app/controllers/v1/tweets_controller.js
Modify: ./app/router.js

The Nodal command line tools have automatically modified your routes and created your controllers for you. The router has automatically added certain standard paths and HTTP methods such as GET and POST. to serve up the API for tweets -- listing, creating, updating, deleting tweets, etc.

Running Migrations

Now that you have a Tweet Model and a Migration, before we start interfacing with our Tweet Model we'll want to make sure the database is ready to handle it. Create the database specified in config/db.json with:

$ nodal db:create

Now, prepare it for migrations and then run those migrations with:

$ nodal db:prepare
$ nodal db:migrate

Interfacing With our Tweets

Make sure your server is running again on localhost with nodal s.

Open http://localhost:3000/v1/tweets in your browser. This is the route that was created automatically by creating the Tweet model. Note that we've automatically pluralized the Tweet model to make the route be "tweets". You should see:

{
  "meta": {
    "total": 0,
    "count": 0,
    "offset": 0,
    "error": null
  },
  "data": []
}

To create a tweet, simply send a POST request to the same endpoint with JSON data or urlencoded data. You can use curl for this. (Also checkout the Postman Chrome Plugin, a visual tool that is great for poking data to and from APIs.)

$ curl --data "user_id=1&body=Testing" http://localhost:3000/v1/tweets

Your response should look something like:

{
  "meta": {
    "total": 1,
    "count": 1,
    "offset": 0,
    "error": null
  },
  "data": [
    {
      "id": 1,
      "user_id": 1,
      "body": "Testing",
      "created_at": "2016-02-20T03:21:30.879Z",
      "updated_at": "2016-02-20T03:21:30.882Z"
    }
  ]
}

Refresh your browser page at http://localhost:3000/v1/tweets and you should see the tweet there.

Creating a User Model

Great! We now have Tweets, but we want some users. In order to handle password encryption (so you don't have to write it yourself), Nodal comes with a pre-baked User model that you can generate with:

$ nodal g:model --user

This User model will be generated with username, email and password fields automatically, along with the bcrypt package for password encryption.

Generate a Controller for your user models with:

$ nodal g:controller v1 --for:User

Migrate your database with:

$ nodal db:migrate

Run your server with nodal s, and send the following POST request to create a user:

$ curl --data "username=test_user&email=test@test.com&password=password" http://localhost:3000/v1/users

You'll get a response like:

{
  "meta": {
    "total": 1,
    "count": 1,
    "offset": 0,
    "error": null
  },
  "data": [
    {
      "id": 1,
      "email": "test@test.com",
      "password": "$2a$10$/pXLNrp9afneJtImvNTBO.79CIsd8N39fko4sF3CaXZyoaxpctQZS",
      "username": "test_user",
      "created_at": "2016-02-20T03:27:58.152Z",
      "updated_at": "2016-02-20T03:27:58.255Z"
    }
  ]
}

Wonderful! We have users, and they have encrypted passwords.

Hiding Sensitive Fields and Model Validations

Visit http://localhost:3000/v1/users in your browser to see a list of all users - you may notice a problem. The API returns the encrypted password. This isn't something we want. To fix this we'll open app/models/user.js:

Find the lines:

User.validates('email', 'must be valid', v => v && (v + '').match(/.+@.+\.\w+/i));
User.validates('password', 'must be at least 5 characters in length', v => v && v.length >= 5);

Underneath them, add:

User.hides('password');

Open http://localhost:3000/v1/users in your browser and voila! Password gone.

You may be wondering what the User.validates(...) calls are about. Well, let's try a new curl request with a password...

$ curl --data "username=test_user&email=test@test.com" http://localhost:3000/v1/users

Awesome! Our validations work as expected. You can try playing along with them on your own.

{
  "meta": {
    "total": 0,
    "count": 0,
    "offset": 0,
    "error": {
      "message": "Validation error",
      "details": {
        "password": [
          "must be at least 5 characters in length"
        ]
      }
    }
  },
  "data": []
}

Joining Users to Tweets in API Responses

You'll notice that in our first Tweet we specified a user_id. We can make sure we join users to Tweets in our API response by doing the following.

First, open app/models/tweet.js:

module.exports = (function() {

  'use strict';

  const Nodal = require('nodal');

  class Tweet extends Nodal.Model {}

  Tweet.setDatabase(Nodal.require('db/main.js'));
  Tweet.setSchema(Nodal.my.Schema.models.Tweet);

  return Tweet;

})();

Before return Tweet, add the lines:

const User = Nodal.require('app/models/user.js');
Tweet.joinsTo(User, {multiple: true});

Now, open app/controllers/v1/tweets_controllers.js and find index():

index() {

  Tweet.query()
    .where(this.params.query)
    .end((err, models) => {

      this.respond(err || models);

  });
}

Change this to:

index() {

  Tweet.query()
    .where(this.params.query)
    .join('user')
    .end((err, models) => {
      this.respond(err || models, ['id', 'body', 'created_at', 'user']);
    });
}

Refresh http://localhost:3000/v1/tweets in your browser and you should see:

{
  "meta": {
    "total": 1,
    "count": 1,
    "offset": 0,
    "error": null
  },
  "data": [
    {
      "id": 1,
      "body": "Testing",
      "created_at": "2016-02-20T03:21:30.879Z",
      "user": {
        "id": 1,
        "email": "test@test.com",
        "username": "test_user",
        "created_at": "2016-02-20T03:27:58.152Z",
        "updated_at": "2016-02-20T03:27:58.255Z"
      }
    }
  ]
}

You may have noticed we passed a second parameter to this.respond(...). That parameter is known as the Model Interface and it tells the Controller which fields of the model to actually display in the API response. By default, the Controller will display all fields with the exception of those you've marked as sensitive / hidden (from above). It will not, however, display any joined models. You'll need to specify these manually (like we've done here, with 'user'). If you want to restrict which fields from the User model show, do the following:

this.respond(err || models, ['id', 'body', 'created_at', {user: ['username']}]);

Joining Tweets to Users

Joining Tweets to Users is a similar process. We'll add to app/models/user.js:

const Tweet = Nodal.require('app/models/tweet.js');
User.joinedBy(Tweet, {multiple: true});

Note: Make sure that joinsTo should always be specified on the child table (whichever table / model has the parent_id field), and joinedBy is always specified on the parent table. These conventions are important.

Similarly, in app/controllers/v1/user_controller.js change your index() method to:

index() {

  User.query()
    .where(this.params.query)
    .join('tweets')
    .end((err, models) => {

      this.respond(
          err || models,
          [
            'id',
            'username',
            'email',
            'tweets'
          ]
        );
    });
}

and you'll see the response:

{
  "meta": {
    "total": 1,
    "count": 1,
    "offset": 0,
    "error": null
  },
  "data": [
    {
      "id": 1,
      "username": "test_user",
      "email": "test@test.com",
      "tweets": [
        {
          "id": 1,
          "user_id": 1,
          "body": "Testing",
          "created_at": "2016-02-20T03:21:30.879Z",
          "updated_at": "2016-02-20T03:21:30.882Z"
        }
      ]
    }
  ]
}

Seeding Your Database

Alright, this is cool, but let's generate some test data!

Open up config/seed.json:

{
  "development": {},

  "test": {},

  "production": {}

}

Modify this to:

{

  "development": {
    "User": [
      {
        "username": "Rihanna",
        "email": "rihanna@r.com",
        "password": "password"
      },
      {
        "username": "The Weeknd",
        "email": "weeknd@w.com",
        "password": "password"
      },
      {
        "username": "Drake",
        "email": "drake@d.com",
        "password": "password"
      }
    ],
    "Tweet": [
      {
        "userid": 1,
        "body": "Hello, world"
      },
      {
        "userid": 2,
        "body": "hello, world!"
      },
      {
        "user_id": 3,
        "body": "You used to call me on my cell phone, world"
      }
    ]
  },

"test": {},

"production": {}

}

We'll now run:

$ nodal db:bootstrap

And visit http://localhost:3000/v1/tweets (make sure server is running):

{
  "meta": {
    "total": 3,
    "count": 3,
    "offset": 0,
    "error": null
  },
  "data": [
    {
      "id": 1,
      "body": "Hello, world",
      "created_at": "2016-02-20T04:08:38.762Z",
      "user": {
        "id": 1,
        "email": "rihanna@r.com",
        "username": "Rihanna",
        "created_at": "2016-02-20T04:08:38.765Z",
        "updated_at": "2016-02-20T04:08:38.765Z"
      }
    },
    {
      "id": 2,
      "body": "hello, world!",
      "created_at": "2016-02-20T04:08:38.764Z",
      "user": {
        "id": 2,
        "email": "weeknd@w.com",
        "username": "The Weeknd",
        "created_at": "2016-02-20T04:08:38.767Z",
        "updated_at": "2016-02-20T04:08:38.767Z"
      }
    },
    {
      "id": 3,
      "body": "You used to call me on my cell phone, world",
      "created_at": "2016-02-20T04:08:38.764Z",
      "user": {
        "id": 3,
        "email": "drake@d.com",
        "username": "Drake",
        "created_at": "2016-02-20T04:08:38.767Z",
        "updated_at": "2016-02-20T04:08:38.767Z"
      }
    }
  ]
}

Awesome! What we've done here is set a database seed by specifying an array of values with which we'll populate each table with in the database. The key for each array (like "User") is just the model name. db:bootstrap is a command that runs nodal db:prepare, nodal db:migrate and nodal db:seed, in that order.

Querying Your Endpoint

The last thing we'll do is start asking our endpoint for different types of results. You may have noticed a .where(this.params.query) on the index() method for both of our controllers. This is creating a filter by through which we selectively choose what results we'd like based on the HTTP query parameters.

For example, try opening these in your browser:

http://localhost:3000/v1/tweets?body=Hello,%20world
http://localhost:3000/v1/tweets?body__is=Hello,%20world
http://localhost:3000/v1/tweets?body__not=Hello,%20world
http://localhost:3000/v1/tweets?body__startswith=Hello
http://localhost:3000/v1/tweets?body__istartswith=Hello
http://localhost:3000/v1/tweets?body__endswith=world
http://localhost:3000/v1/tweets?user__username=Drake

Supported comparators by the PostgreSQL adapter (required __ before them) are:

is
not
lt
lte
gt
gte
contains
icontains
startswith
istartswith
endswith
iendswith
like
ilike
is_null
not_null
in
not_in
json
jsoncontains

Keep note that the default is __is, and you can query joined models by using the join name (i.e. user) and separating the field with double underscores as well (__).

Enjoy, and Keep Exploring!

That ends the tutorial for now. Remember you can find a completed version of everything outlined here at keithwhor/instatweet-api. There's a lot more you can do with Nodal, including this GraphQL demo, but there should be enough material here to get you started.

Check out the Nodal website and Star the repository on GitHub to keep up to date as the project grows and progresses. We welcome you to join the community! Our Gitter channel is a great place to get quick responses and pointers.

Additionally, you can follow along with a set of screencasts that walk through very similar material based on Nodal 0.6.

Thanks for reading. :) You can follow me on Twitter at @keithwhor.

Keith Horwood

About Keith Horwood

Keith is a self-taught developer from Toronto, Canada with a passion for learning and building. Formerly the Engineering Lead at Storefront, his most recent adventure is building Polybit, a service that allows to developers to more effectively build and deploy their application backends so they can focus on their product and their customers.

Recent Features

  • By
    CSS Filters

    CSS filter support recently landed within WebKit nightlies. CSS filters provide a method for modifying the rendering of a basic DOM element, image, or video. CSS filters allow for blurring, warping, and modifying the color intensity of elements. Let's have...

  • By
    Interview with a Pornhub Web Developer

    Regardless of your stance on pornography, it would be impossible to deny the massive impact the adult website industry has had on pushing the web forward. From pushing the browser's video limits to pushing ads through WebSocket so ad blockers don't detect them, you have...

Incredible Demos

  • By
    CSS Vertical Centering

    Front-end developing is beautiful, and it's getting prettier by the day. Nowadays we got so many concepts, methodologies, good practices and whatnot to make our work stand out from the rest. Javascript (along with its countless third party libraries) and CSS have grown so big, helping...

  • By
    WebKit-Specific Style:  -webkit-appearance

    I was recently scoping out the horrid source code of the Google homepage when I noticed the "Google Search" and "I'm Feeling Lucky" buttons had a style definition I hadn't seen before:  -webkit-appearance.  The value assigned to the style was "push-button."  They are buttons so that...

Discussion

  1. Nice post! Nodal is very similar to rails and rake, cool!
    To complement this post, last week I published an ebook about how to build apis with Node.js and ES6, using Sequelize, Express and Passport, I hope you enjoy it!

    https://leanpub.com/building-apis-with-nodejs

    • jatinrana

      I think it is not work to stuck in kind of hard cording simply use beauty full themes like divi which let u customizes your site without touching any cord.

  2. This looks really good and I’m particularly interested in the GraphQL implementation. I’m also curious about the reasoning for creating your own ORM instead of using something like Sequelize or Bookshelf and also passing on some a framework like Express or Koa.

  3. I remember building an api service using restify a couple of years ago. Your implementation looks way better and very straight forward. Feels a little bit like Rails! Great job Keith! Thanks for your work!

  4. This looks great! I’ll definitely be checking back once the docs are a little more filled out.

  5. Andrew

    Express.js API (according https://www.cleveroad.com/blog/the-best-node-js-framework-for-your-project–express-js–koa-js-or-sails-js) also took advantage of the Node.js manager package node to distribute and install countless third-party plug-ins. You can easily add OAuth integration / social logins to the web application without much hassle, using this authentication middleware to connect.

    • Thank you for this article. In my opinion, Node.js has more disadvantages than advantages, such as unsuitability for processor-intensive tasks, unstable API, lack of a standard library, and most codes will have problems with JavaScript, an asynchronous programming model is required, which is more difficult for many developers.

  6. George Toth

    This looks great! I’ll definitely be checking back once the docs are a little more filled out.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!