RESTful Services Made Simple: How to Get Started in 3 Steps

DZone 's Guide to

RESTful Services Made Simple: How to Get Started in 3 Steps

In this article, we'll cover how the launch and implementation procedures you'll need to know to get your RESTful service off the ground.

· Integration Zone ·
Free Resource

Looking for a quick-start guide to set up your web applications? Here you go!

In our previous article, we unveiled an easy way to achieve modern RESTful services. In this article, we will proceed to launch and implementation details.

First, let’s remember that our key goal is to make a quality, fast-working microservice with as little effort as possible. And yes, to get rid of extra IT staff, extra latency and extra architecture components—in other words, all of the flaws that the typical microservice architecture has.

You can achieve this by using a 5-to-2 approach, that is transforming five components into two. Here are the original five:

  • A web proxy, ex. NGINX, to handle slow clients (with an unstable Internet connection) and serve static content.
  • An application server, such as Apache or Django, to implement business logic.
  • A caching system, like Memcached or Redis, to cache content from your DBMS.
  • A database proxy or a DB connection pool for your DBMS to be really efficient.
  • A DBMS itself to store your data.

But, you can reduce those five to just two: a proxy server and a storage (DBMS) tandem.

Tarantool will be your storage option for this two-tier setup because it includes an application server, a caching system, and a DBMS all in one instance!

There are two ways to set this up with Tarantool: either simply via the Tarantool HTTP module or by adding NGINX using the Tarantool NGINX upstream module.

REST on Tarantool…With Tarantool-HTTP

So let’s begin by taking a look at the Tarantool-HTTP option, which can be installed via a package manager or GitHub. It is hardly an excellent tool for daily database challenges, being far slower than the NGINX module, especially with lots of data. It also has no HTTPS and is missing other HTTP functions. Nevertheless, it can be useful in some cases, especially for tests or admin needs.

Let’s make an application that creates a blank table upon startup, then launches an HTTP server, which will increment a counter for each visiting IP address on '/' and return all addresses in JSON format. First, in the /etc/tarantool/instances.enabled directory, make an app1.lua file:

-- Creates a blank table at startup and an index
box.once('schema', function()
    box.space.hosts:create_index('primary', { type = 'hash',
       parts = {1, 'str'} })

-- The GET request handler to / (web server root directory)
local function handler(self)
    -- Get the client's IP address
    local ipaddr = self.peer.host
    -- Insert a new entry for the address or increment the existing one
    box.space.hosts:upsert({ ipaddr, 1 }, {{'+', 2, 1}})
    -- Return all rows as JSON to the client
    return self:render{ json = box.space.hosts:select() }

local httpd = require('http.server')
local server = httpd.new('', 8080)
server:route({ path = '/'  }, handler)

An important note: box.cfg{} configures and starts the built-in database (“box”), after which you can create tables (called a “space” in Tarantool), execute queries, etc. Tarantool instances can also be started using, for example, “tarantoolctl start app1,” which piggybacks on the Linux init system. You can make as many apps as you want: the init system itself will run the required number of Tarantool daemons and monitor them.

However, Tarantool developers recommend running slightly fewer instances than the number of physical cores you have on the server — this provides the best performance. So far so good, right?

...or Use the Alternative: nginx_tarantool_upstream

As mentioned, a user can also send REST or JSON RPC requests to Tarantool through NGINX by using the Tarantool upstream module. The latter can connect directly to one Tarantool instance, or balance a workload among many of them. NGINX and Tarantool communicate via a MessagePack-based protocol.

Our web service will work in the same address space as the database.

So, let's take a simple database (for example, a list of users) and implement the four basic types of REST requests:

  • GET

  • PUT

  • POST


We’ll show how it works with request and response examples and the data will return in JSON format — which is the simplest and most logical option.

The NGINX upstream module provides REST, a JSON API, WebSockets, and load balancing.

Here are three simple steps to get started:

Step 1. Install NGINX and the Tarantool NGINX upstream module. Here is an NGINX setup example (a piece of NGINX config, its location depends on your distro):

upstream tnt {
  # Tarantool host

server {
  listen 8081 default;
  location /tnt {
    # REST mode on
    tnt_http_rest_methods get post put patch delete; # or all

    # Pass http headers and uri
    tnt_pass_http_request on;

    # Module on
    tnt_pass tnt;

The module’s main functions are:

  • It activates the NGINX configuration files with the “tnt_pass UPSTREAM_NAME” directive.

  • It provides fast HTTP + JSON <-> Tarantool Protocol streaming, resulting in minimum locking of NGINX workers (for parsing).

  • It has non-blocking NGINX I/O in both directions.

  • As a nice bonus: it maintains all NGINX and NGINX upstream features.

  • It allows calling Tarantool stored procedures via a JSON-based protocol.

  • Data is delivered via HTTP(S) POST, which is convenient for modern web apps (and others).

Input data:

[ { "method": STR, "params":[arg0 ... argN], "id": UINT }, ...N ]

 "method" The stored procedure's name. This must match the procedure name in Tarantool. For example, to call the Lua function  do_something (a, b), you use: "method": "do_something."

"params" Arguments of a stored procedure. For instance, to pass arguments to the Lua function do_something (a, b), you use: "params": ["1", "2"].

"id"  The numeric identifier set by the client.

Output data:

[ { "result": JSON_RESULT_OBJECT, "id":UINT, "error": { "message": STR, "code": INT } }, ...N ]

"result"  The data returned by the stored procedure. For example, if the Lua function do_something (a, b) returns {1, 2} then this would yield "result": [[1, 2]].

"id" The numeric identifier set by the client.

"error" If an error occurs, this field will tell you why.

You can read more details about the protocol here.

Step 2. Install Tarantool and create the instance (which is a Lua program file, echo.lua in our case).

Everything is quite easy here so there shouldn't be any problems. Here's our example:

box.cfg {
    log_level = 5;
    listen = 9999;

box.once('grant', function()
    box.schema.user.grant('guest', 'read,write,execute', 'universe')

-- Table
local users = box.schema.space.create('users', {if_not_exists=true})
-- Indexes
users:create_index('user_id', {if_not_exists=true})

function add_user(user_first_name, user_last_name)
  return users:auto_increment{user_first_name, user_last_name}

function add_user_ex(user_first_name, user_last_name, ...)
  return users:auto_increment{user_first_name, user_last_name, ...}

function get_user_by_id(user_id)
  return users.index.user_id:get{user_id}

function get_users()
  return users:select{}

function echo(a)
  if type(a) == 'table' then
    return  {{a}}
  return {a}

Step 3. Test it. Well, NGINX feels good with its Tarantool connector and we have our Tarantool instance functions implemented. It's high time to run a test request using wget.

So, let's add a user:

Image title

All is running well so far. Easy, isn't it?

You may be thinking: "What if my service works for a fairly long time, but I'm likely to expand and change its functions, so that several API versions may appear?" Well, you can, for example, specify the API version as part of the path in the query. In general, there is a lot more to know, even just about the above three steps. But we’ll leave them for future articles, so stay tuned!

integration, lua, nginx, restful services, tarantool

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}