RESTful Endpoints aka APIs

Little Bit Technologies

--

Introduction

Speedy is a Rapid Application Development platform that offers quickest way to develop, and host RESTful APIs. It does this via RESTful APIs itself, in other words, the entire development lifecycle of RESTful API is done over /rest endpoint of Speedy itself. All the defined API/endpoints become available instantaneously over /uri/rest/. All of the Speedy’s RESTful APIs have database in common and provide CRUD abstraction over it. Custom business logic is supported with Java Script programming.

Supported Keywords

The following are the set of keywords to be used with definition creation.

uri     - URI on which this def will be available
do - Do this when called for SQL/Func
method - HTTP Method type GET/PUT/POST/DELETE
version - Version of this definition
blang - Language used for functions js/tengo
before - Execute this func before the 'do' part
bver - Version of the before function
alang - Language used for functions js/tengo
after - Execute this function after the 'do' part
aver - Version of the after function
doc - Documentation associated with the definition

Speedy definitions

Let’s consider few examples to understand how to create a definition and make calls to it. We would like to draw your attention to one point before we proceed any further. Words like ‘definition’, ‘endpoint’ and ‘API’ all mean one and the same in the end, they represent the stages of metamorphosis which leads to a RESTful API creation. A “definition” is the code written to build an “endpoint”, and “API” is how an “endpoint” is invoked or executed.

Speedy RESTful structure

Consider the following table, under the PostgreSql schema tiger. You don’t need to create this, speedy in evaluation mode creates this schema, table and sample rows automatically. This is given just for reference.

CREATE SCHEMA tiger;

CREATE TABLE IF NOT EXISTS tiger.employee(
-- Employee ID, automatically incremented
id SERIAL PRIMARY KEY,
-- Name of the employee
name VARCHAR(100) NOT NULL,
-- Date of join, time stamp
doj TIMESTAMP NOT NULL,
-- Employee is currently active or not
active BOOLEAN NOT NULL DEFAULT true,
-- Date of exit, if employee is not active
doe TIMESTAMP
);

CREATE INDEX idx_tiger_emp_status ON tiger.employee(active);

Let’s go ahead and create ‘definitions’ to perform CRUD operations on this table.

Creating a Speedy definition

First we shall build an API to create a new employee record. In SQL to do that we can use the following statement.

INSERT INTO tiger.employee(name, doj) VALUES ('Scott Mayer', NOW());

Now, let’s convert this SQL statement into a Speedy ‘definition’. If you are planning on using Postman put the following into body of the request; in general beware of double single quote escapes, '\'' kind.

!{ uri }!
sample/tiger/employee

!{ do }!
INSERT INTO tiger.employee(name, doj) VALUES ('?:name', NOW()) RETURNING id;

!{ version }!
1

!{ method }!
post

For curl the following payload is just fine, just be mindful of this factor throughout the article.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee

!{ do }!
INSERT INTO tiger.employee(name, doj) VALUES ('\''?:name'\'', NOW()) RETURNING id;

!{ version }!
1

!{ method }!
post'

That’s it! Now let’s go over it in detail. curl --location --request POST 'localhost:3023/rest' implicitly means we are “creating” a RESTful definition on /rest endpoint, of HTTP method type POST. One could be running the Speedy server anywhere, in such case localhost should be replaced with the right IP address or FQDN. The ‘definition’ details are enclosed within --data-raw let’s understand it one by one.

All keywords within Speedy starts and ends with !{ and }!. This has been introduced from v.6.8 onwards to help developers with better debug message while creating JavaScript/Tengo based functions. They not only offer better code readability but also offer easy development/maintainence cost. While crafting the actual payload the positional parameters aka custom keywords can also be enclosed within these, bare with it for a minute or two, it will be clear shortly.

"INSERT INTO tiger.employee(name, doj) VALUES ('?:name', NOW()) RETURNING id;, this is our template SQL statement. ?:name, tells Speedy that when actual payload is sent, look for a field by name “name”, and use its value inside the SQL statement. Observe that SQL statement has RETURNING id; at the end. This is a very nice feature of PostgreSQL which returns the auto-incremented employee id value, upon INSERT.

"sample/tiger/employee", with this we are informing Speedy that we would like to associate, the SQL statement given above on this path.

"version" -> "1", is self-explanatory; associates “1” as version number for this API definition. Speedy definition version is string type, so one can fancy any naming nomenclature, for example, “v1”, “v.1.0”, “v/1” are all valid version numbers. The same must be used while invoking the API too. We will see that in a while.

"method" -> "post", informs Speedy to make this definition available only with HTTP method POST. Now, let’s give the API a spin.

Invoking the API

curl -L -X POST 'localhost:3023/uri/rest/sample/tiger/employee' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ name }!
Ken Thompson

!{ version }!
1'

Above command in curl invokes the API we just created. Notice the URI we are trying to reach, it is the same one we just defined as part of the definition. Definitions are created on /rest and they are hosted automatically on /uri/rest instantaneously.

Now for the second part. Look through the payload that is being sent to the API endpoint. The payload has name and version in it. This is precisely what we talked about custom keywords in the payload. Version has the same value we used during creation of ‘definition’ above. This helps Speedy to choose the right definition to be invoked for the same URI. “name”, is a part of Speedy’s keywords, if you notice. It comes from the definition itself, in the “do” section of definition it is mentioned, as part of template. That’s how dynamic values are bind into SQL statements. Executing the above curl command gives the following output (something similar to it).

{"id":30}

This means that our API invocation was successful, and employee ID assigned to “Ken Thompson” is 30. Simple, right!

An API to read

So far we have covered the “Create” part, now let’s create few definitions to “Read” too.

List all active employees.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee

!{ do }!
SELECT * FROM tiger.employee;

!{ version }!
1

!{ method }!
get'

Invoke the API

curl -L -X GET 'localhost:3023/uri/rest/sample/tiger/employee?version=1'

Output

[
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:29.873735Z",
"id": 28,
"name": "Dunes High"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:41.948271Z",
"id": 29,
"name": "Mighty Joe"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:50.84025Z",
"id": 30,
"name": "Armstrong Joe"
}
]

Just like magic APIs are ready to use. Now is a good time to notice how we didn’t deploy any physical code, restart web-server, nor did any of the usual developer antics. That’s how simple Speedy is.

List employees with parameterised status.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee

!{ do }!
SELECT * FROM tiger.employee WHERE active = ?:status;

!{ version }!
1

!{ method }!
get'

And the request and output.

curl -X GET 'localhost:3023/uri/rest/sample/tiger/employee?version=1&status=false'

Notice how the “?:status” in the SQL query was picked up from the URI query parameter! Speedy intelligently understands all these and works its magic.

[
{
"active": false,
"doe": "2022-10-29T16:39:38.413288Z",
"doj": "2021-01-19T09:39:38.113288Z",
"id": 4,
"name": "Jane Doe"
},
{
"active": false,
"doe": "2022-10-29T16:39:58.04551Z",
"doj": "2020-02-19T10:19:51.03551Z",
"id": 5,
"name": "Dow Jones"
}
]

API to Update/Modify

Now let’s cover the other part of CRUD, i.e. update.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee/lop

!{ do }!
UPDATE tiger.employee SET active = false, doe = NOW() WHERE id = ?:empid;

!{ version }!
1

!{ method }!
put'

That’s a simple ‘definition’ to mark an employee going on ‘lapse-of-pay’. Now let’s use it to put ‘Mike Elis’ on LOP.

curl -X PUT 'localhost:3023/uri/rest/sample/tiger/employee/lop' \
-H 'Content-Type: application/json' \
-d '{
!{ empid }!
2

!{ version }!
1'

Let’s check it!

curl -X GET 'localhost:3023/uri/rest/sample/tiger/employee?version=1&status=false'

We can see that ‘Mike’ is not longer among the ‘active’ employees list.

[
{
"active": false,
"doe": "2022-10-29T16:39:38.413288Z",
"doj": "2022-10-29T16:39:38.413288Z",
"id": 4,
"name": "Jane Doe"
},
{
"active": false,
"doe": "2022-10-29T16:39:58.04551Z",
"doj": "2022-10-29T16:39:58.04551Z",
"id": 5,
"name": "Dow Jones"
},
{
"active": false,
"doe": "2022-10-30T08:26:26.073998Z",
"doj": "2022-10-29T14:54:48.329645Z",
"id": 2,
"name": "Mike Elis"
}
]

API to Delete

Now let’s cover the other part of CRUD, i.e. delete.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee/exit

!{ do }!
DELETE FROM tiger.employee WHERE id = ?:empid;

!{ version }!
1

!{ method }!
delete'

That’s a simple ‘definition’ to mark an employee who is leaving the organisation; in reality such informations are preserved with status, this is just a tutorial for practise. Now let’s use it to send-off ‘Mike Elis’.

curl -X DELETE 'localhost:3023/uri/rest/sample/tiger/employee/exit' \
-H 'Content-Type: application/json' \
-d '
!{ empid }!
2

!{ version }!
1'

Let’s check it!

curl -X GET 'localhost:3023/uri/rest/sample/tiger/employee?version=1&status=false'
[
{
"active": false,
"doe": "2022-10-29T16:39:38.413288Z",
"doj": "2022-10-29T16:39:38.413288Z",
"id": 4,
"name": "Jane Doe"
},
{
"active": false,
"doe": "2022-10-29T16:39:58.04551Z",
"doj": "2022-10-29T16:39:58.04551Z",
"id": 5,
"name": "Dow Jones"
}
]

Advanced Topics

In this section let’s look at some of the advanced coding with Speedy. We assure you that it will be simple, as it is so far!

Custom Logic with Speedy

Let’s take a look at a well-designed RESTful API, the following figure depicts one such in logical blocks.

A well-designed RESTful API structure

Within the ‘code’ of most of the APIs, there are three sections, first, pre-processing of given user data like validation, transformation, adding composite or derivative fields etc., second, persist the information with database or get results from it or both, third, apply some custom logic to the obtained data and return the resultant to User.

So far we have seen direct SQL statement bindings to the URI with Speedy. How do we build some custom logic around it? Speedy offers simple and elegant solution to that via JavaScript functions. Pre-processing business logic could be wired with an independent JavaScript function with the help of before family of keywords, and Post-processing logic in the same way with after family of keywords.

As a recap, before family has blang and bver keywords, and after family similarly has alang and aver. before/ after holds the name of the function, while blang/ alang holds the language in which the function is implemented, for now it is always js. bver/ aver holds the version of the function respectively.

Engaging “before" to action

Let’s define a simple name swapping function with the following curl command.

curl -L -X POST 'localhost:3023/func' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/swapname

!{ name }!
swapname

!{ do }!
function swap(a) {
var c = a['\''name'\''].split('\'' '\'');
a['\''name'\''] = c[1] + '\'' '\'' + c[0];
}

swap(args);

!{ lang }!
js

!{ version }!
1
'

The entry point function within the JavaScript code should always take a parameter with the name args only, as Speedy will pass the data over it. Now let’s execute the function to see whether it works!

curl -L -X POST 'localhost:3023/uri/func/sample/tiger/swapname' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ name }!
Wiss Jane

!{ lang }!
js

!{ version }!
1
'

And the result is,

{
"lang": "js",
"name": "Jane Wiss",
"version": "1"
}

Notice that it does what we have coded it for, that is, swap the first and second part of the name. Speedy always returns the entire body that is sent to it as part of the reply for Function endpoints. This is done in-order to make sure dynamic version on the server doesn’t affect the client. It’s a feature not a bug!

Now, let’s wire this function with our “Create” definition, defined earlier. Consider the following code for reference.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee/func/before

!{ do }!
INSERT INTO tiger.employee(name, doj) VALUES ('\''?:name'\'', NOW()) RETURNING id;

!{ version }!
1

!{ method }!
post

!{ before }!
sample/tiger/swapname

!{ blang }!
js

!{ bver }!
1'

Give it a go, with the following curl command.

curl -L -X POST 'localhost:3023/uri/rest/sample/tiger/employee/func/before' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ name }!
Jackson Tike

!{ version }!
1'

By querying the result using the previous GET call we get the following. As we can see Speedy invoked the JavaScript function seamlessly and modified the supplied name from “Jackson Tike” to “Tike Jackson” before inserting it into the database table.

[
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:29.873735Z",
"id": 28,
"name": "Dunes High"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:41.948271Z",
"id": 29,
"name": "Mighty Joe"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:50.84025Z",
"id": 30,
"name": "Armstrong Joe"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T20:31:53.110039Z",
"id": 31,
"name": "Ken Thompson"
},
{
"active": true,
"doe": null,
"doj": "2023-01-08T22:28:03.726877Z",
"id": 36,
"name": "Tike Jackson"
}
]

Pro Tip: database functions and procedures can do a lot more than often one can imagine. If a certain thing must be one on a large set of data at the database level, consider using ‘Procedure’ or ‘Function’. Here are links to PostgreSql Procedure (https://bit.ly/3sHb1Km) and Function (https://bit.ly/3Wk3hvi). We have done some amazing timesaving and performant coding using them.

Engaging “after” to action

Let’s go ahead and write a similar functionality to swap of name that we did earlier. But, this time let’s apply it against a result from a SQL query from database.

curl -L -X POST 'localhost:3023/func' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/reversename

!{ name }!
reversename

!{ do }!
function swap(a) {
var c = a['\''name'\''].split('\'' '\'');
a['\''name'\''] = c[1] + '\'' '\'' + c[0];
}

function rearrange(m) {
m.forEach(swap);

return m;
}

function extract(r) {
var m = JSON.parse(r['\''result'\'']);
r['\''result'\''] = JSON.stringify(rearrange(m));
}

extract(args)

!{ lang }!
js

!{ version }!
1'

Few important concepts to understand with this example.

  • Speedy will pass the request (body sent for the API call) and result from database as map or Object in JavaScript terms to the entry point function.
  • The database result is assigned as a value to key result on the args JS Object. This is will in JSON format, but not in Object format, it will be a plain string.
  • So, it becomes imperative to convert the JSON string to Object and back to string with JSON.parse and JSON.stringify functions of JavaScript.
  • JavaScript function used for after keyword, must remember to set the value back, provided that is desired.

Now let’s create a ‘definition’ which will use this ‘function’ in runtime.

curl -L -X POST 'localhost:3023/rest' \
-H 'Content-Type: text/plain' \
--data-raw '
!{ uri }!
sample/tiger/employee/func

!{ do }!
SELECT * FROM tiger.employee WHERE active = true;

!{ version }!
1

!{ method }!
get

!{ after }!
sample/tiger/reversename

!{ alang }!
js

!{ aver }!
1'

Time to call the API endpoint to put the whole thing into action.

curl -L -X GET 'localhost:3023/uri/rest/sample/tiger/employee/func?version=1'
[
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:29.873735Z",
"id": 28,
"name": "High Dunes"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:41.948271Z",
"id": 29,
"name": "Joe Mighty"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:50.84025Z",
"id": 30,
"name": "Joe Armstrong"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T20:31:53.110039Z",
"id": 31,
"name": "Thompson Ken"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T20:32:19.5654Z",
"id": 32,
"name": "Thompson Ken"
}
]

Notice how the JavaScript function changed the First and Last name of every record before sending it back. Here is the original output without the function.

curl -L -X GET 'localhost:3023/uri/rest/sample/tiger/employee/func?version=1'
[
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:29.873735Z",
"id": 28,
"name": "High Dunes"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:41.948271Z",
"id": 29,
"name": "Joe Mighty"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T13:36:50.84025Z",
"id": 30,
"name": "Joe Armstrong"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T20:31:53.110039Z",
"id": 31,
"name": "Thompson Ken"
},
{
"active": true,
"doe": null,
"doj": "2023-01-07T20:32:19.5654Z",
"id": 32,
"name": "Thompson Ken"
}
]

It is also possible to use both before and after in a single “definition”, depending on the usecase.

--

--

No responses yet