Building a CRUD API with Total.js: A Step-by-Step Guide
Total.js is a powerful web application framework for Node.js, providing an excellent foundation for building robust APIs. In this tutorial, we'll guide you through the process of creating a CRUD (Create, Read, Update, Delete) API for managing a collection of books using Total.js.
Table of Contents
- Prerequisites
- Setting Up the Project
- Defining API Routes
- Creating the 'Books' Schema
- Testing the API
- Testing API Routing for WebSockets
- Additional Resources
1. Prerequisites
Before we start, make sure you have Node.js and npm installed on your machine. You can install Total.js by running:
2. Setting Up the Project
Create a new file named index.js
in your project folder and add the following code to enable debugging:
// /index.js
require('total4/debug')({ port: 4000 });
3. Defining API Routes
Now, let's define the routes for our CRUD operations. In the controllers/api.js
file, set up the standard RESTful routing and additional conventions for API routing:
// /controllers/api.js
exports.install = function() {
// Standard **RESTful** routing
ROUTE('GET /api/books/ *Books --> query'); // Query all books
ROUTE('GET /api/books/{id}/ *Books --> read'); // Read a specific book by ID
ROUTE('POST /api/books/ *Books --> insert'); // Insert a new book (+ expects payload data, validated in schema)
ROUTE('PUT /api/books/{id}/ *Books --> update'); // Update a specific book by ID (+ expects payload data, validated in schema)
ROUTE('DELETE /api/books/{id}/ *Books --> remove'); // Remove a specific book by ID (- does not expect payload data)
// **API** routing using different conventions
// Using hyphen notation for **API** endpoint names
ROUTE('API /api/ -books *Books --> query'); // **API**: Query all books (- does not expect payload data)
ROUTE('API /api/ -books_read/{id} *Books --> read'); // **API**: Read a specific book by ID (- does not expect payload data)
ROUTE('API /api/ +books_insert *Books --> insert'); // **API**: Insert a new book (+ expects payload data, validated in schema)
ROUTE('API /api/ +books_update/{id} *Books --> update'); // **API**: Update a specific book by ID (+ expects payload data, validated in schema)
ROUTE('API /api/ -books_remove/{id} *Books --> remove'); // **API**: Remove a specific book by ID (- does not expect payload data)
// **API** routing for websockets using at notation
ROUTE('API @api -books *Books --> query'); // **API Websocket**: Query all books (- does not expect payload data)
ROUTE('API @api -books_read/{id} *Books --> read'); // **API Websocket**: Read a specific book by ID (- does not expect payload data)
ROUTE('API @api +books_insert *Books --> insert'); // **API Websocket**: Insert a new book (+ expects payload data, validated in schema)
ROUTE('API @api +books_update/{id} *Books --> update'); // **API Websocket**: Update a specific book by ID (+ expects payload data, validated in schema)
ROUTE('API @api -books_remove/{id} *Books --> remove'); // **API Websocket**: Remove a specific book by ID (- does not expect payload data)
// For WEBSOCKET Routing
ROUTE('SOCKET /api/ @api', 1024 * 5); // Handle websockets on the '/api/' path using at notation
}
4. Creating the 'Books' Schema
Define the schema for the 'Books' entity in the schemas/books.js
file. This schema will include actions for querying, reading, inserting, updating, and removing books:
// /schemas/books.js
// Create an empty array to store book data
var Books = [];
// Define a new schema for 'Books'
NEWSCHEMA('Books', function(schema) {
// Action for querying books
schema.action('query', {
name: 'Listing books',
query: 'search:String',
action: function($) {
// Callback with the array of books
$.callback(Books);
}
});
// Action for reading a specific book
schema.action('read', {
name: 'Read a specific book',
params: '*id:UID',
action: function($) {
var params = $.params;
// Find the book by ID in the array
var item = Books.findItem('id', params.id);
if (!item) {
// If the book is not found, return a 404 error
$.invalid(404);
return;
}
// Callback with the found book
$.callback(item);
}
});
// Action for inserting a new book
schema.action('insert', {
name: 'Insert new book',
input: '*title:String,description:String,price:Number,author:String,year:String',
action: function($, model) {
// Generate a unique ID and set the creation timestamp
model.id = UID();
model.dtcreated = NOW;
// Create a search string for the book
model.search = (model.title + ' ' + model.description + ' ' + model.author + ' ' + model.year).toSearch();
// Add the new book to the array
Books.push(model);
// Callback with the ID of the inserted book
$.done(model.id)();
}
});
// Action for updating a book
schema.action('update', {
name: 'Update book',
params: '*id:String',
input: '*title:String,description:String,price:Number,author:String,year:String',
action: function($, model) {
var item = Books.findItem('id', $.params.id);
if (!item) {
// If the book is not found, return a 404 error
$.invalid(404);
return;
}
// Update the book with the new data
model.dtupdated = NOW;
model.search = (model.title + ' ' + model.description + ' ' + model.author + ' ' + model.year).toSearch();
for (var el in model)
item[el] = model[el];
// Callback with the ID of the updated book
$.done(model.id)();
}
});
// Action for removing a book
schema.action('remove', {
name: 'Remove book',
params: '*id:String',
action: function($) {
var params = $.params;
// Find the index of the book in the array
var index = Books.findIndex('id', params.id);
console.log(index);
if (index === -1) {
// If the book is not found, return a 404 error
$.invalid(404);
return;
}
// Remove the book from the array
Books.remove(item => item.id == index);
// Callback to indicate successful removal
$.done()();
}
});
});
5. Testing the API
Now that our API is set up, it's time to test it. Start your application by running:
You can then use tools like Postman or cURL to interact with your API and perform CRUD operations on the 'Books' entity.
6. Testing API Routing for WebSockets
With Total.js, testing WebSocket functionality is made easy. The framework allows you to test API routing for WebSockets using messages. Here's an example WebSocket message that you can use for testing:
{
"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": {}
}
}
Message Components:
- TYPE: Indicates that this is an API-related message.
- callbackid: A custom callback ID that will be returned back in the response.
- data: Contains information about the API request.
- schema: Specifies the schema name and dynamic arguments along with query parameters.
- data: An object containing additional data for the API request.
Example Usage:
- Connecting to WebSocket:
Use a WebSocket client or tool to connect to the WebSocket endpoint of your Total.js application.
- Sending the WebSocket Message:
Send the provided WebSocket message to the server. Make sure to replace placeholders such as CUSTOM_CALLBACK_ID
, schema_name
, {dynamic_arg_1}
, {dynamic_arg_2}
, and arguments
with actual values.
{
"TYPE": "api",
"callbackid": "CUSTOM_CALLBACK_ID",
"data": {
"schema": "books_read/123456789/query?search=Total.js",
"data": {}
}
}
This example message is querying the books_read
schema for a book with the ID 123456789
and includes a search query parameter.
- Receiving the Response:
The server will process the WebSocket message, execute the corresponding API route, and send a response back. Look for the response, including the custom callback ID, to verify the success of the API routing.
By following this approach, you can effectively test and debug your WebSocket-based API routes in Total.js.
Additional Resources
For a comprehensive understanding of Total.js API routing mechanisms and features, refer to the official Total.js documentation
This documentation provides in-depth insights into:
Explore the documentation to enhance your knowledge and leverage the full potential of Total.js for building powerful and scalable APIs.
Conclusion
Congratulations! You've successfully created a CRUD API using Total.js. This simple yet powerful framework allows you to build scalable and maintainable APIs with ease.
Feel free to customize and extend this project according to your specific requirements. Happy coding!