/
Restful web services (/wsapi)

Restful web services (/wsapi)



Check out Profound API.  It offers all these features plus many more.



Creating restful web services is enabled through the creation of the file/table DbDefn module.

The web service routes will automatically be prefixed with /wsapi.

  • ProfoundJS: a get list would look like: http://server/wsapi/customers

  • Profound.js Spaces: a get list would look like: https://spaces.profoundjs.com/user/workspace/wsapi/customers



Here is an example of a file/table called "order_line" that is on a mySQL database, and use using all of the built-in rest services.

module.exports = { useSQL: true, dbObject: "order_line", uniqueKey: [ { field: "order_id", ascending: true }, { field: "line_id", ascending: true } ], routes: { add: "/orderdetails", get: "/orderdetails/:order/:line", getList: "/orderdetails/:order", update: "/orderdetails/:order/:line", delete: "/orderdetails/:order/:line" } }



You do not need to have anything in this module file unless you are using a database other than an IBM i and you will also use the built-in CRUD methods :: get, getList, add, update, and delete.

The only thing that was altered was the addition of the "routes" section.

These above routes are all of the standard built-in functions.

  • add uses the POST method

  • get and getList use the GET method

  • update uses the PUT method

  • deleted uses the DELETE method



If the add and update were not included, no web service would exist for them.

Any of these can be overridden by defining the route differently (see below).



Here is how a complete example of that same order_line file/table but with the built-in "add" function overridden with a custom find function that can be used as a web service or by any pjs module because the function is exported.

function customAdd(request, response) { pjs.defineTable("orderline", { keyed: true, add: true }); var qty = parseInt(request.body.qty) || 1; // Make sure the QTY is >= 1 request.body.qty = qty < 1 ? 1 : qty; var result = orderline.write(null, request.body); response.send(result); } function findOne(request, response) { pjs.defineTable("orderline", { keyed: true, read: true }); var order = request.order || request.query.order || request.param.order; var line = request.line || request.query.line || request.param.line; var data = orderline.getRecord(order, line); if (response) response.send(data); else return data; } module.exports = { useSQL: true, dbObject: "order_line", uniqueKey: [ { field: "order_id", ascending: true }, { field: "line_id", ascending: true } ],   findOne: findOne, routes: { add: { path:"/orderdetails", handler: customAdd }, // notice the handler property is set to this "customAdd" function. get: "/orderdetails/:order/:line", findOne: { method: "get", path:"/orderdetails/find"}, // notice that handler property is not set, but the name "findOne" is an exported function, so it will auto connect them. getList: "/orderdetails/:order", update: "/orderdetails/:order/:line", delete: {path: "/orderdetails/:order/:line", authorize:"apiAuthenticate.js"}, // notice the authorize property is set to a javascript file. This file is yours to create and maintain. It will be called before this action. } }



All functions that use any of the PJS APIs should be declared outside of the of the exports section.

Make sure your function names do not match the existing RLA functions, because the RLA functions will take precedence.

You can define the routes in a few different manners.

  • name: "url path"

    • The route method will default to POST (unless it is one of the built-in functions - see above)

    • The name is also the name of the function to be called


    Here is an example of creating your own web services.  The function named is changeQuantity, so the the name of the route should be the same.

function changeQuantity(request, response) { var result = null; pjs.defineTable("orderline", { keyed: true, read: true, update: true, delete: true }); var newQty = parseInt(request.body.qty) || 0; // If QTY is <= 0 then delete it, else update it if (newQty <= 0) result = orderline.delete([request.params.order, request.params.line]); else { var item = orderline.getRecord([request.params.order, request.params.line]); item.qty = newQty; result = orderline.update(null, item); } response.send(result); }   module.exports = { ... changeQuantity: changeQuantity, // This will allow this function to be available to other modules routes: { ... changeQuantity: "/orderdetails/:order/:line" } }



Routes are unique based on the route method + path.  In the above example this route would resolve to be the same as the built-in update function.  The Profound.js web service registration program will only register the 1st occurrence of this route, any other duplicates will be logged as errors. This uniqueness rule is across the board for all DbDefn module, all of them need to be unique.

There are a few very simple ways to work around this:

  1.  

    1. Change the path to be more distinct --> changeQuantity: "/orderdetails/:order/:line/updqty"

    2. See next way to define a route which allow you to define the route method





  • name: {method: "route method", path: "url path" }

function changeQuantity(request, response) { var result = null; pjs.defineTable("orderline", { keyed: true, read: true, update: true, delete: true }); var qty = parseInt(request.body.qty) || 0; // If QTY is <= 0 then delete it, else update it if (qty <= 0) result = orderline.delete([request.params.order, request.params.line]); else { orderline.getRecord([request.params.order, request.params.line]); result = orderline.update(null, request.body); } response.send(result); }   module.exports = { ... changeQuantity: changeQuantity, // This will allow this function to be available to other modules routes: { ... changeQuantity: { method: "patch", path: "/orderdetails/:order/:line" }, } }

 

  • name: {method: "route method", path: "url path", handler: "the function" }

    • Same as above with the option to override the function to be called

      • The function value should be the actual function object

      • Because this definition has both the function and path, the name is only needed to be unique to pass javascript rules.

function changeQuantity(request, response) { var result = null; pjs.defineTable("orderline", { keyed: true, read: true, update: true, delete: true }); var qty = parseInt(request.body.qty) || 0; // If QTY is <= 0 then delete it, else update it if (qty <= 0) result = orderline.delete([request.params.order, request.params.line]); else { orderline.getRecord([request.params.order, request.params.line]); result = orderline.update(null, request.body); } response.send(result); }   module.exports = { ... changeQuantity: changeQuantity, // This is only be needed if you want to expose this function to other modules routes: { ... updQty: {method: "patch", path: "/orderdetails/:order/:line", handler: changeQuantity } } }



Example of apiAuthentication.js
function authenticate(request, response, next) { const Fiber = require('profoundjs-fibers'); // Do what is needed to authenticate/authorize the user:: pjs.query, pjs.call, ... // For example, say a request contains an API key in a header // And you have a table that contains API keys and values for API Users // This authentication logic might look something like this: let filter = pjs.data.createCondition("apiKey", "=", request.headers["api-key"]); let user = pjs.data.get("apiUsers", filter, 1); if (!user) throw new pjs.NotAuthorizedError(); Fiber.current.session.user = user.name; // "next() -- is what will call your webservice next(); } exports.run = authenticate;