A simple Heroku-based example of FunctionScript API Development
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:
- Automatic type coercion
- Automatic error handling
- Differentiation between required / optional parameters
- 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.
You can deploy to Heroku instantly by clicking the button below.
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!
There are four API endpoints that serve different purposes to introduce you to the basics of FunctionScript
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
};
};
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}!`;
};
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;
};
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
}
};
};
Thanks to those who helped with the example!
FunctionScript is (c) 2019 Polybit Inc.