In this tutorial, we will show how easy it is to create a headless shopping cart API using Total.js framework. In the following Chapter 2. we will show how easy you can connect a browser with Total.js WebComponents and in our case we will use the webcomponent j-ShoppingCart from https://componentator.com/components/j-shoppingcart/.
Getting Started
To get started we take a moment to explain what actualy will happen in the next chapters.
Creating a headless shoppingcart api without any layout and just data is the way to get started writing modern and independent interfaces. Total.js comes in a way, making it a easy task in everbody's daily work. Following the notation of Total.js, you will get all of the benefits the framework provides to you.
The second chapter will just go in detail how to implement and use the Total.js WebComponent j-ShoppingCart and how you can easly integrate the layout of your choice.
The WebComponent is prepared to be synchronize the data object as a singleton. Including the functionality of synchronizing data between the LocalStorage
and the server.
In the third chapter of this series, we will extend the single page application and deliver it as a progressive web application.
1. create project folder
We create a new folder for our project and change into it.
$ mkdir totaljs-cart-api
$ cd totaljs-cart-api
2. install Nodejs framework Totaljs v4
We install the Totaljs v4 framework using the nodejs package manager
npm
.
3. create folder structure
We create the necessary folder structure for controllers and schemas:
$ mkdir controllers schemas definitions databases
4. create a config file
Optional but helpful is a ./config file. If there is a config file in the root folder of the application, this file is read when the application is started and can influence the application and the behavior at runtime.
(optional)
$ echo 'name : <YOURAPPNAME>' > config
Here are some more informations about Total.js Framework Configurations:
5. create a startup file
We create the index.js startup file in the root folder of the project:
// in /index.js
const options = {};
// options.ip = '127.0.0.1';
// options.port = parseInt(process.argv[2]);
// options.unixsocket = require('path').join(require('os').tmpdir(), 'app_name');
// options.unixsocket777 = true;
// options.config = { name: 'Total.js' };
// options.sleep = 3000;
// options.inspector = 9229;
// options.watch = ['private'];
// options.livereload = 'https://yourhostname';
// Enables cluster:
// options.cluster = 'auto';
// options.cluster_limit = 10; // max 10. threads (works only with "auto" scaling)
// Enables threads:
// options.cluster = 'auto';
// options.cluster_limit = 10; // max 10. threads (works only with "auto" scaling)
// options.timeout = 5000;
// options.threads = '/api/';
// options.logs = 'isolated';
var type = process.argv.indexOf('--release', 1) !== -1 || process.argv.indexOf('release', 1) !== -1 ? 'release' : 'debug';
require('total4/' + type)(options);
6. Create the API controller
We create the file
/controllers/api.js
in the controllers directory and we use the ROUTE() function of Total.js:
// in /controllers/cart.js
// general cart routes with websocket implementation
exports.install = function() {
// SHOPPING CART REST API implementation
ROUTE('API /api/ -cart_query *Cart --> query');
ROUTE('API /api/ -cart_read/{id} *Cart --> read');
ROUTE('API /api/ +cart_insert/ *Cart --> insert');
ROUTE('API /api/ +cart_update/{id} *Cart --> update');
ROUTE('API /api/ -cart_remove/{id} *Cart --> remove');
// Websocket implementation
ROUTE('API @api -cart_query *Cart --> query');
ROUTE('API @api -cart_read/{id} *Cart --> read');
ROUTE('API @api +cart_insert *Cart --> insert');
ROUTE('API @api +cart_update/{id} *Cart --> update');
ROUTE('API @api -cart_remove/{id} *Cart --> remove');
// IMPORTANT: socket route
ROUTE('SOCKET / @api');
};
We recommend this introduction of Louis to learn more about Total.js API routing. Totaljs API Routing
Totaljs documentation:
7. NoSQL or TextDB as DB interface
Total.js framework provides a built-in NoSQL system or TextDB that we can use as a database interface. To use this, we add the appropriate functions to our controller or schema.
TextDB definition
This type of TextDB stores documents (objects) with the predefined schema. In other words: you can store documents with fields only that are defined in the Table schema. The table is much faster than the NoSQL database, but it's limited due to the declaring of the schema.
- TABLE(name) returns TextDB instance
- each database needs defined schema in /config file like example below:
Table schema declared in /config file:
table_YOURNAME : id:uid,name:string,price:number,dtcreated:date
table_cart : id:uid,name:string,items:object,price:number,total:number,count:number,dtcreated:date
table_products : id:uid,name:string,desc:string,price:number,total:number,count:number,date:date
8. create cart schema with built-in NoSQL
In the folder
/schemas
we create a the cart schema file named cart.js:
// in /schemas/cart.js
NEWSCHEMA('Cart', function(schema) {
// define Action
schema.action('query', {
action: function($, model){
NOSQL('cart').find().callback(function(err, response) {
if (err) {
$.invalid(err);
} else
$.callback(response);
});
}//end action
});
schema.action('read', {
input: '*id:string',
action: function($, model){
$.callback([]);
}
});
//
schema.action('insert', {
input: '*name:string,*price:number',
action: function($, model){
NOSQL('cart').insert($.body).callback(function(err, response) {
if (err) {
$.invalid(err);
} else
$.success();
});// end NOSQL
}//end action
});
schema.action('update', {
input: '*id:string, *amount:number',
action: function($, model){
NOSQL('users').update($.body).where('id', $.params.id).callback(function(err, response) {
if (err) {
errorHandler(err, res);
return;
} else
$.success();
});
}//end action
});
schema.action('remove', {
input: '*id:string',
action: function($, model){
NOSQL('cart').remove().where('id', $.params.id).callback(function(err, response) {
if (err) {
$.invalid(err);
} else
$.success();
});
}//end action
});
});
9. Testing the API
You can use any REST API client tool like Curl, Postman or SWAGGER.
Let's say we have /api/cart_insert
endpoint for adding products using $ curl
:
- Use the "POST" request type
- Enter the full URL http://localhost:8000/api
- choose request body-type "JSON"
- Enter the api schema action like cart_insert
- Enter the data object with a product data singleton in JSON format
Curl POST JSON Syntax:
Method: POST
Content-Type: application/json
{
"schema": "schema_name/{dynamic_arg_1}/{dynamic_arg_2}?query=arguments",
"data": {}
}
$ curl -X POST [URL]
-H "Content-Type: application/json"
-d "[JSON data]"
Curl POST JSON Example:
To follow the definition of j-ShoppingCart singleton, we use the following json definition:
https://componentator.com/components/j-shoppingcart/
{
items: [
{
name: String,
price: Number,
total: Number,
count: Number,
date: Date
}
],
price: Number,
total: Number,
count: Number
}
# declare cart items in singleton structure
$ export CART_ITEMS='{"items": [{name: "product 1", price: 8.00, total: 8.00, count: 1 }], price: 8.00, total: 8.00, count:1}'
# declare api call object
$ export API_OBJECT='{"schema":"cart_insert", "data":'${CART_ITEMS}'}'
#
$ curl -X POST http://<your-ip-adress>:<your-port>/api/ \
-H 'Content-Type: application/json' \
-d $API_OBJECT
Response object
{"success":true, "id": <returned-insert-id>}
Preparing CRUD testing
Inserting cart items.
# declare api create call object
$ export API_CREATE='{"schema":"cart_insert", "data":'${CART_ITEMS}'}'
# CRUD - CREATE API Call
$ curl -X POST http://<your-ip-adress>:<your-port>/api/ \
-H 'Content-Type: application/json' \
-d $API_CREATE
Read the API with specific cart item id {"schema":"cart_read/1234567890"}
# declare api read call object
$ export API_READ='{"schema":"cart_read/<id>"}'
# CRUD - READ API Call
$ curl -X POST http://<your-ip-adress>:<your-port>/api/ \
-H 'Content-Type: application/json' \
-d $API_READ
cURL UPDATE API Call
# declare api update call object
$ export API_UPDATE='{"schema":"cart_update", "data":'${CART_ITEMS}'}'
# CRUD - update api call
$ curl -X POST http://<your-ip-adress>:<your-port>/api/ \
-H 'Content-Type: application/json' \
-d $API_UPDATE
cURL DELETE API Call
# declare api remove call object
$ export API_DELETE='{"schema":"cart_delete/<id>"}'
# CRUD - delete api call
$ curl -X POST http://<your-ip-adress>:<your-port>/api/ \
-H 'Content-Type: application/json' \
-d $API_DELETE
WebSocket message:
An api websocket payload looks like:
{
"TYPE": "api",
"callbackid": "CUSTOM_CALLBACK_ID", // The value will be returned back
"data": {
"schema": "schema_name/{dynamic_arg_1}/{dynamic_arg_2}?query=arguments",
"data": {}
}
}
Look to the difference building the json object to send to the websocket api implementation
{
"TYPE": "api",
"callbackid": "123456789", // The value will be returned back
"data": {
"schema": "cart_insert",
"data": $CART_ITEMS
}
}
How to test the websocket api implementation:
It can be tricky, but with this cURL command it should be possible get in touch with the Total.js Websocket API Implementation
# Testing the websocket api connection
curl --include \
--header "Connection: Upgrade" \
--header "Upgrade: websocket" \
--header "Sec-WebSocket-Key: qwerty" \
http://<your-ip-address>:<your-port>/
Advance the API with a payment provider
How to implement a payment gateway provider like paypal, molly or stripe?
If you are interessted in it, just leave a line at info@totaljs.com and we will get in touch.
Conclusion:
With Total.js, we can create a powerful API in a short time. The framework's clear structure and intuitive methods speed up the development process significantly. Inlcuding a realtime websocket based api structure, it is now easy to connect with several clients written in Rust, Go, PHP, C/C++, Ruby and more.
Happy coding !