A lightweight serverless functions engine for Node.js built on Express and Socket.IO.
Run HTTP routes, real‑time Socket.IO events, global/route guards (middleware), and scheduled jobs — all defined as plain JavaScript modules. Use it locally with a folder of functions, or deploy by pulling functions from Git, an npm tarball, or a remote URL. Ships with static assets serving, health and discovery endpoints, and a simple programmatic API.
- npm: https://www.npmjs.com/package/bfast-function
- License: MIT
- Runtime: Node.js 18+
- HTTP functions (Express handler shape)
- Socket.IO event functions (per‑namespace handlers)
- Guards (Express middleware) at
/or a specific path - Scheduled jobs (cron‑like with
node-schedule) - Static assets served at
/assets - Function auto‑discovery from one or more folders
- Multiple deployment modes:
local,git,npm,url - Health
GET /functions-healthand discoveryGET /functions-all - Optional custom
startScriptto run any process instead of the built‑in server
Install bfast tools cli and create a starter project
npm i -g bfast-tools
bfast fs create <name-of-project> #Example : bfast fs create my-bfast-projectCreate your functions (CommonJS or ESM). Each exported value is a descriptor object.
Example: functions/example.js
// CommonJS
module.exports.hello = {
// Optional: defaults to /functions/hello
path: '/hello',
// Optional: defaults to Express "all" method
method: 'get',
// Standard Express handler
onRequest: (req, res) => {
res.status(200).send('Hello, World!');
}
}
module.exports.guardAll = {
// Mount at root as a global middleware
path: '/',
onGuard: (req, res, next) => {
// your auth/logging/etc
return next();
}
}
module.exports.sampleEvent = {
// Socket.IO namespace AND event name
name: '/echo',
onEvent: (request, response) => {
// request = { auth, body }
response.emit({echoed: request.body});
}
}
module.exports.sampleJob = {
// node-schedule rule (runs every minute)
rule: '* * * * *',
onJob: () => {
console.log('job tick:', new Date().toISOString());
}
}Start a server (ESM):
// index.mjs
import { start } from 'bfast-function';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
await start({
port: '3000',
functionsConfig: {
// You can pass one folder or an array of folders
functionsDirPath: `${__dirname}/functions`,
// Optional: location of bfast.json (see “Configuration”)
bfastJsonPath: `${__dirname}/bfast.json`,
// Optional: custom path where static files live
// assets: `${__dirname}/assets`
}
});Run it:
node index.mjsNow:
- HTTP:
GET http://localhost:3000/hello→ "Hello, World!" - Health:
GET http://localhost:3000/functions-health - Discovery:
GET http://localhost:3000/functions-all - Static:
GET http://localhost:3000/assets/...(if you placed files there)
Use this ready‑to‑go example repo to bootstrap a simple bfast project:
Repo: https://github.com/joshuamshana/BFastFunctionExample
Option A — Local development (clone + run your own entry file)
# 1) Clone the template
git clone https://github.com/joshuamshana/BFastFunctionExample.git
cd BFastFunctionExample
npm iIf the template does not include an entry script, create index.mjs:
import { start } from 'bfast-function';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
await start({
port: '3000',
functionsConfig: {
functionsDirPath: `${__dirname}/functions`,
// optional: bfastJsonPath: `${__dirname}/bfast.json`
// optional: assets: `${__dirname}/assets`
}
});Run it and try endpoints:
node index.mjs
curl http://localhost:3000/functions-health
curl http://localhost:3000/functions-all
# If the template provides an HTTP export named `hello` with no explicit `path`:
curl http://localhost:3000/functions/helloOption B — No code: run in Git mode pointing at the template
PORT=3000 MODE=git \
GIT_CLONE_URL=https://github.com/joshuamshana/BFastFunctionExample.git \
node node_modules/bfast-function/src/start.mjs
# Then test
curl http://localhost:3000/functions-health
curl http://localhost:3000/functions-allFunctions are discovered by scanning the folder(s) you pass in functionsDirPath for *.js, *.mjs, *.cjs (recursively). Each export must be an object with one of the following shapes.
export const myApi = {
// Optional: defaults to /functions/myApi
path: '/api',
// Optional: one of: get|post|put|patch|delete|all (default "all")
method: 'post',
// Express signature
onRequest: (req, res) => {
res.json({ok: true});
}
}Notes:
- If
pathis omitted, the route becomes/functions/<exportName>. methodis lower‑cased internally and defaults toall(Express’app.all).
export const secure = {
// "/" mounts globally, any other path mounts at that prefix
path: '/',
onGuard: (req, res, next) => {
// deny example
// return res.status(401).send('Unauthorized');
return next();
}
}export const chat = {
// Used as namespace and event name
name: '/chat',
onEvent: (request, response) => {
// request: { auth, body }
// response helpers:
// - emit(data)
// - broadcast(data)
// - announce(data) // to the whole namespace
// - emitTo(socketId, data)
// - topic(topicName).join()
// - topic(topicName).broadcast(data)
// - topic(topicName).announce(data)
}
}Client example:
import { io } from 'socket.io-client';
const s = io('http://localhost:3000/chat');
s.on('/chat', (packet) => {
// packet = { body: ... }
console.log(packet.body);
});
s.emit('/chat', { auth: {token: 'x'}, body: { msg: 'hi' } });export const everyMinute = {
rule: '* * * * *', // node-schedule format
onJob: () => {
console.log('tick');
}
}If present, used to control function discovery ignores. Example:
{
"ignore": [
"**/node_modules/**",
"**/specs/**",
"**/*.specs.js",
"**/*.specs.mjs",
"**/*.specs.cjs"
]
}- Place it at the path you pass in
functionsConfig.bfastJsonPath. - If omitted, sensible defaults are used.
The engine accepts an Options object:
interface Options {
port?: string; // default '3000'
mode?: 'git'|'npm'|'url'|'local';
gitCloneUrl?: string; // for mode 'git'
gitUsername?: string; // for private repos
gitToken?: string; // for private repos
npmTar?: string; // for mode 'npm' (package name to pack)
urlTar?: string; // for mode 'url' (direct .tgz URL)
functionsConfig?: {
functionsDirPath: string | string[]; // one or more folders
assets?: string; // static files root
bfastJsonPath?: string; // path to bfast.json
};
startScript?: string; // if set, run this instead of starting HTTP server
}See the source for details: src/models/options.mjs.
You can populate the functions directory in four ways.
- Local (recommended for development)
await start({
port: '3000',
functionsConfig: { functionsDirPath: '/abs/path/to/functions' }
});Note: When functionsConfig.functionsDirPath is provided, local functions are used and remote modes are skipped (the mode value is effectively ignored).
- Git
await start({
port: '3000',
mode: 'git',
gitCloneUrl: 'https://github.com/you/your-functions.git',
gitUsername: process.env.GIT_USERNAME, // optional
gitToken: process.env.GIT_TOKEN // optional
});- npm tarball (package name)
await start({
port: '3000',
mode: 'npm',
npmTar: 'your-functions-package'
});- Remote URL (.tgz)
await start({
port: '3000',
mode: 'url',
urlTar: 'https://example.com/your-functions.tgz'
});Notes:
- When using
git, dependencies inside the functions folder are installed (npm install --omit=dev). - When using
npm/url, the tarball is extracted and normalized before deployment.
You can boot the engine without writing code using env vars that map to the Options fields. The built‑in starter reads:
PORT(default3000)MODE(git|npm|url|local, defaultgit)GIT_CLONE_URL,GIT_USERNAME,GIT_TOKENNPM_TARURL_TARSTART_SCRIPT
Example:
PORT=3000 MODE=local START_SCRIPT="" node node_modules/bfast-function/src/start.mjsOr with Git:
PORT=8080 MODE=git \
GIT_CLONE_URL=https://github.com/you/your-functions.git \
GIT_USERNAME=you GIT_TOKEN=xxxx \
node node_modules/bfast-function/src/start.mjsBuild (if you cloned this repo):
docker build -t bfast-function .Run (local functions folder mounted):
docker run --rm -p 3000:3000 \
-e MODE=local \
-e PORT=3000 \
-v "$(pwd)/functions:/faas/functions" \
bfast-functionRun (Git mode):
docker run --rm -p 3000:3000 \
-e MODE=git \
-e PORT=3000 \
-e GIT_CLONE_URL=https://github.com/you/your-functions.git \
-e GIT_USERNAME=you -e GIT_TOKEN=xxxx \
bfast-functionContainer entrypoint executes npm run start, which invokes the same env‑driven starter as above.
Static files are served from /assets.
Resolution order:
- If you pass
functionsConfig.assets, that path is used. - Else, if the current working directory contains
assets/, that folder is served. - Else, a built‑in fallback folder is used for development.
GET /functions-health→{ "message": "running" }GET /functions-all→ JSON map of all discovered functions (by export name)
- ESM vs CommonJS: your function files can be
.js,.mjs, or.cjs. The loader attempts dynamicimport()first, then falls back torequire(). - Missing routes? Ensure your exports are objects and contain
onRequest,onEvent,onGuard, oronJobwithrule. - Default paths: if
pathis omitted for HTTP functions, the route is/functions/<exportName>. - Socket.IO: event
nameis both the namespace and the event key. - Jobs:
rulemust be present and valid pernode-schedule. - Ignoring files: provide a
bfast.jsonwithignoreglobs if needed.
- Options shape:
src/models/options.mjs - Core runtime:
src/core.mjs - Function discovery:
src/controllers/resolver.mjs - Mounting & deployment:
src/controllers/index.mjs
Issues and PRs are welcome. Please read CONTRIBUTING.md and our CODE_OF_CONDUCT.md.
MIT © BFast Cloud