functionscript-server

by FunctionScript

GitHub Readme.md

functionscript-server

A simple Heroku-based example of FunctionScript API Development

FunctionScript

Introduction

You can use this repository to build a brand new FunctionScript API in a flash. It's the simplest way to build an API, routing is handled automatically by the file structure. HTTP GET and POST requests are handled identically, treating each endpoint with a more imperative, or command-based approach. Instead of HTTP POST /users/ to create a user resource, for example, we'd recommend a /createUser/ endpoint.

A simple summary is that all JavaScript files in the ./functions folder will be automatically turned into API endpoints with:

  1. Automatic type coercion
  2. Automatic error handling
  3. Differentiation between required / optional parameters
  4. Embedded support for API Schemas

The example contained here is intended for deployment on Heroku, and used the FunctionScript Daemon and Gateway to handle requests. Though FunctionScript is primarily intended to be used alongside serverless compute models, including being the primary development model for Standard Library APIs, the Gateway is easily manipulable to be able to handle requests on a standalone server -- and Heroku is the best place to get started.

Installation

You can deploy to Heroku instantly by clicking the button below.

Deploy

If you're feeling adventurous, you can set up the server locally by following these instructions:

$ git clone git@github.com:FunctionScript/functionscript-server.git
$ cd functionscript-server
$ npm install
$ npm start

That's it! You're done!

API Endpoints

There are four API endpoints that serve different purposes to introduce you to the basics of FunctionScript

./function/__main__.js

Reads ./pages/index.html and uses it as a simple HTML template. In this example, you're introduced to the {object.http} type for returning HTTP responses. The default response type is JSON.

const fs = require('fs');
const PAGE = fs.readFileSync('./pages/index.html').toString();

/**
* This API endpoint simply serves a static HTML page.
*   It replaces all instances of `{%varname}` with corresponding GET / POST
*   parameters. The default is to respond to the `name` parameter
* @param {string} name A name to enter.
* @returns {object.http} response The HTTP response
*/
module.exports = async (name = 'world', context) => {
  let render = PAGE.replace(/\{\%([\w]+)\}/gi, ($0, $1) => context.params[$1]);
  return {
    headers: {
      'Content-Type': 'text/html'
    },
    body: render
  };
};

./functions/add.js

Adds two numbers together with a hello message, returns JSON string. This example shows you the built-in FuncitonScript error-handling using required parameters.

/**
* A simple API that adds two numbers together. `a` and `b` are required parameters
*   and the API will automatically throw an error if they are not provided
* @param {string} name A name to enter.
* @param {number} a The first of two numbers to add.
* @param {number} b The second of two numbers to add.
* @returns {string} message A simple hello mesage
*/
module.exports = async (name = 'world', a, b) => {
  return `Hello ${name}, ${a} + ${b} = ${a + b}!`;
};

./functions/sms.js

Uses utils/sms to send an SMS message. This example shows off a little of the power of Standard Library, a central registry of FunctionScript-powered APIs. You can obtain your own STDLIB_SECRET_TOKEN by registering on Standard Library.

const lib = require('lib')({token: process.env.STDLIB_SECRET_TOKEN});

/**
* Sends an SMS using https://stdlib.com/@utils/lib/sms
* @param {string} tel A telephone number to send SMS to
* @param {string} body A message body to send
* @returns {object} result The result of the SMS
*/
module.exports = async (tel, body) => {
  let result = await lib.utils.sms['@1.0.11']({
    to: tel,
    body: `Testing from FunctionScript Server:\n${body}`
  });
  return result;
};

./functions/static/__notfound__.js

Routes static resources from ./static/ directory based on pathname. This is a comprehensive example that reads from multiple different folders and serves different HTTP headers ("Content-Type") based on the filenames it's serving. It is also an introduction to the __notfound__.js handler.

const mime = require('mime');

const fileio = require('../../helpers/fileio.js');

let filepath = './static';
let staticFiles = fileio.readFiles(filepath);

/**
 * This endpoint handles all routes to `/static` over HTTP, and maps them to the
 *  `./static` directory (part of the root dir)
 * It is a special example of a "NOT FOUND" handler, which any request not
 *   matching an existing functions/ API endpoint will be routed to
 * `context.path` can be used to retrieve the path name
 * @returns {object.http}
 */
module.exports = async (context) => {

  // Hot reload for local development
  if (process.env.NODE_ENV !== 'release') {
    staticFiles = fileio.readFiles(filepath);
  }

  let pathEnd = context.path.slice().pop();
  let staticFilepath = context.path.slice(1).join('/');
  let file = pathEnd.indexOf('.') !== -1
    ? staticFiles[staticFilepath]
    : (
      staticFiles[[staticFilepath, 'index.htm'].filter(v => !!v).join('/')] ||
      staticFiles[[staticFilepath, 'index.html'].filter(v => !!v).join('/')]
    );

  if (!file) {
    return {
      statusCode: 404,
      body: '404 - Not Found',
      headers: {
        'Content-Type': 'text/plain'
      }
    };
  }

  let cacheControl = process.env.NODE_ENV === 'release'
    ? 'max-age=31536000'
    : 'max-age=0';

  return {
    statusCode: 200,
    body: Buffer.from(file),
    headers: {
      'Content-Type': mime.getType(staticFilepath),
      'Cache-Control': cacheControl
    }
  };
};

Acknowledgements

Thanks to those who helped with the example!

Contributors

FunctionScript is (c) 2019 Polybit Inc.