I leverage NetSuite RESTlets pretty extensively for several of my projects. When I got tired of keeping track of multiple RESTlet endpoints in various config files for numerous apps, I decided to change my approach.
To do this, I needed to first come up with a “framework” of sorts that allowed me to define these operations. I created a simple request handler that took the request, determined the operation function it needed to call, then wrapped the call to ensure errors were always logged to our external logging server.
This is the interface all POST requests to the RESTlet conform to.
export interface ApiRequest<T> {
operation: string;
payload: T;
}
Next, these are the interfaces implemented by each “operation handler”.
interface OperationHandlerCallback<T> {
(req: T): any;
}
interface OperationHandler<T> {
name: string;
callback: OperationHandlerCallback<T>;
}
Finally, this is the “API request handler” that takes in the request and calls the appropriate operation handler.
export class ApiRequestHandler {
private _opHandlers: OperationHandler<any>[] = [];
constructor(private _log: Logger) { }
addOperation<T>(name: string, callback: OperationHandlerCallback<T>): void {
const existing = this._getOpHandler(name);
if (existing) {
this._opHandlers.splice(this._opHandlers.indexOf(existing), 1);
}
this._opHandlers.push({ name, callback });
}
handleRequest<T>(req?: ApiRequest<T>): NSErrorResponse | any {
if (!req) {
return new NSErrorResponse(NSErrorCodes.BAD_REQUEST, "Request body cannot be empty!");
}
const opHandler = this._getOpHandler(req.operation);
if (!opHandler) {
return new NSErrorResponse(NSErrorCodes.SCRIPT_ERROR, `Failed to find a suitable request handler for "${req.operation}".`);
}
let result = null;
try {
result = opHandler.callback(req.payload);
}
catch (err) {
result = new NSErrorResponse(NSErrorCodes.SCRIPT_ERROR, err.message);
this._log.error(`Failed to handle API request.`, {
err,
req
});
}
finally {
this._log.flushEvents();
}
return result;
}
private _getOpHandler(name: string): OperationHandler<any> | undefined {
const matches = this._opHandlers.filter(x => x.name === name);
if (matches.length > 0) {
return matches[0];
}
return undefined;
}
}
To use the request handler, we simply add one or more operations, then ask it to handle a request!
/**
* Called by NetSuite when the RESTlet receives a POST request.
*
* @param requestBody The body of the POST request.
*/
export function post(requestBody?: ApiRequest<any>): any | NSErrorResponse {
const log = LoggerFactory.getLogger();
const api = new ApiRequestHandler(log);
api.addOperation<any>("helloWorld", x => {
return "Hello World!";
});
return api.handleRequest(requestBody);
}
Josh Johnson
Latest posts by Josh Johnson (see all)
- TFW Windows Interrupts Your Service - October 30, 2018
- XPath and IMT: Namespace Prefixes - October 29, 2018
- Modular RESTlets - October 26, 2018