If GraphQL, JSON-RPC and React server actions had a baby
Spiceflow is the fastest way to write and expose an RPC API. In Spiceflow any files with the directive 'use spiceflow'
will be processed as an API route, each function defined in the file will be exposed as an JSON-RPC method.
After defining your functions you can call spiceflow serve
to start a server exposing your API, you can also espose the API using the Next.js or your own server.
When calling spiceflow build
spiceflow will generate a client side SDK for your API, you can use it to call your API from the browser. This client SDK will be type safe, because the functions are called the same way from the server and the client, so types can be reused (after being bundled with @microsoft/api-extractor).
You can publish this SDK to npm and let your users interact with your API in an easy and type safe way.
npm i spiceflow
# create a new spiceflow project, works best in a monorepo
npx spiceflow init --name my-api
# .
# ├── package.json
# ├── src
# │ ├── index.ts
# │ └── v1
# │ ├── example.ts
# │ └── generator.ts
# └── tsconfig.json
npx spiceflow serve # builds the sdk in the dist folder and starts serving your API
npm run try-sdk # try using the sdk
'use spiceflow'
directive will be processed as an API route, each function defined in the file will be exposed as an JSON-RPC method.// src/v1/functions.ts
'use spiceflow';
export async function spiceflowFunction() {
return { hello: 'world' };
}
export async function* spiceflowGenerator() {
for (let i = 0; i < 10; i++) {
await sleep(300);
yield { i };
}
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
spiceflow serve --watch
import {
spiceflowFunction,
spiceflowGenerator,
} from './my-api/dist/v1/functions';
// will call the server with fetch
const { hello } = await spiceflowFunction();
for await (const { i } of spiceflowGenerator()) {
console.log(i);
}
Spiceflow has 3 ways to serve your API:
spiceflow serve --port 3333
Note: You will need to call
spiceflow build
before using the SDK when using this method
// pages/api/spiceflow/[...slug].tsx
import { nodeJsHandler } from './my-api/server';
export default async function handler(req, res) {
return await nodeJsHandler({ req, res, basePath: '/api/spiceflow' });
}
// pages/api/spiceflow/[...slug]/route.tsx
import { edgeHandler } from './my-api/server';
export const POST = edgeHandler;
After exposing your server you will need to rebuild your client sdk using that url:
spiceflow build --url http://localhost:3000/api/spiceflow # the Next.js app url
This plugin injects the req
and res
objects in an AsyncLocalStorage
context, so you can access them in your server functions:
'use spiceflow';
import { getNodejsContext } from 'spiceflow/context';
export async function serverAction({}) {
const { req } = getNodejsContext();
const host = req?.headers.get('host');
return { host };
}
You can export a function named wrapMethod
to easily wrap all your server actions with error logging or other wrappers
'use spiceflow';
export function wrapMethod(fn) {
return async (...args) => {
try {
const res = await fn(...args);
return res;
} catch (error) {
console.error(error);
throw error;
}
};
}
export async function failingFunction({}) {
throw new Error('This function fails');
}
You can create a v1
folder in your project and exports your function from index.ts
, when you want to release a breaking version of your API, you can create a new folder and change the imports in index.ts
file. This way the sdk users will always use the latest version of your API, while old SDK users will keep the old version.
Spiceflow build
command transpiles the files with the use spiceflow
directive so that any exported function will use fetch to send arguments and get the result, the the transformed files are saved in the dist
folder.Other files are compiled to the dist directory using tsc
.
Spiceflow also bundles the type definitions with @microsoft/api-extractor
so the generated dist files don't rely on external local packages and can be safely published to npm.