GRIP library for JavaScript, provided as express
and hono
compatible middleware.
This library is designed to assist the creation of backend server applications written in JavaScript that utilize GRIP. This library is usable with the following frameworks:
Supported GRIP servers include:
Authors: Katsuyuki Omuro [email protected], Konstantin Bokarius [email protected]
ServeGripBase
no longer declaresmonkeyPatchResMethodsForWebSocket
andmonkeyPatchResMethodsForGripInstruct
abstract methods. Instead, at the end of therun
method, theonAfterSetup()
method is called.
- Now adds support for Hono framework.
GRIP is a protocol that enables a web service to delegate realtime push behavior to a proxy component, using HTTP and headers.
@fanoutio/serve-grip
is a server middleware that works with frameworks such as Express and
Hono. It:
- gives a simple and straightforward way to configure these frameworks against your GRIP proxy
- parses the
Grip-Sig
header in any requests to detect if they came through a Grip proxy - provides your route handler with tools to handle such requests, such as:
- access to information about whether the current request is proxied or is signed
- methods you can call to issue any instructions to the GRIP proxy
- provides access to the
Publisher
object, enabling your application to publish messages through the GRIP publisher.
Additionally, serve-grip
also handles
WebSocket-Over-HTTP processing so
that WebSocket connections managed by the GRIP proxy can be controlled by your route handlers.
Install the library.
npm install @fanoutio/serve-grip
Import the ServeGrip
class from @fanoutio/serve-grip/node
and instantiate the middleware. Then install it before
your routes.
Example:
import express from 'express';
import { ServeGrip } from '@fanoutio/serve-grip/node';
const app = express();
const serveGripMiddleware = new ServeGrip(/* config */);
app.use(serveGripMiddleware);
app.use('/path', (res, req) => {
if (req.grip.isProxied) {
const gripInstruct = res.grip.startInstruct();
gripInstruct.addChannel('test');
gripInstruct.setHoldStream();
res.end('[stream open]\n');
}
});
app.listen(3000);
Note
It's strongly recommended to use TypeScript when working with Hono.
Import the serveGrip
function from @fanoutio/serve-grip/hono
instantiate the middleware. Then install it before
your routes.
Specifying the configuration object for serveGrip
can be done in a callback. This is useful for environments such
as Fastly Compute, where configuration may not be available until request processing.
Additionally, import the Env
type and use it when instantiating Hono
. This enables type
checking for the c.var.grip
context variable.
Example:
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { serveGrip, type Env } from '@fanoutio/serve-grip/hono';
const app = new Hono<Env>();
const serveGripMiddleware = serveGrip(() => /* config */);
app.use(serveGripMiddleware);
app.use( '/path', async (c) => {
if (c.var.grip.isProxied) {
const gripInstruct = c.var.grip.startInstruct();
gripInstruct.addChannel(CHANNEL_NAME);
gripInstruct.setHoldStream();
return c.text('[stream open]\n');
}
});
serve({ fetch: app.fetch, port: 3000 }, (addr) => {
console.log(`Example app listening on port ${addr.port}!`)
});
Note
The above example is for Hono running on Node.js. Hono can be used with other server platforms as well. For details on adapting the application to other platforms, see Hono's guide.
For examples of using this library with Hono on a backend application running on Fastly Compute, check out the following example applications:
Import the ServeGrip
class from @fanoutio/serve-grip/node
and instantiate it. The Koa middleware is available
as the .koa
property on the object. Install it before your routes.
Example:
import Koa from 'koa';
import Router from '@koa/router';
import { ServeGrip } from '@fanoutio/serve-grip/node';
const app = new Koa();
const serveGripMiddleware = new ServeGrip(/* config */);
app.use(serveGripMiddleware.koa);
const router = new Router();
router.use( '/path', ctx => {
if (ctx.req.grip.isProxied) {
const gripInstruct = res.grip.startInstruct();
gripInstruct.addChannel('test');
gripInstruct.setHoldStream();
ctx.body = '[stream open]\n';
}
});
app.use(router.routes())
.use(router.allowedMethods());
app.listen(3000);
You may use this library to add GRIP functionality to your Next.js API Routes.
Import the ServeGrip
class from @fanoutio/serve-grip/node
and instantiate the middleware, and then run it in
your handler before your application logic by calling the async function serveGripMiddleware.run()
.
Example:
/lib/grip.js
:
import { ServeGrip } from '@fanoutio/serve-grip/node';
export const serveGripMiddleware = new ServeGrip(/* config */);
/pages/api/path.js
:
import { serveGripMiddleware } from '/lib/grip';
export default async(req, res) => {
// Run the middleware
if (!(await serveGripMiddleware.run(req, res))) {
// If serveGripMiddleware.run() has returned false, it means the middleware has already
// sent and ended the response, usually due to an error.
return;
}
if (req.grip.isProxied) {
const gripInstruct = res.grip.startInstruct();
gripInstruct.addChannel('test');
gripInstruct.setHoldStream();
res.end('[stream open]\n');
}
}
Note
In Next.js, you must specifically call the middleware from each of your applicable API routes. This is because in Next.js, your API routes will typically run on a serverless platform, and objects will be recycled after each request. You are advised to construct a singleton instance of the middleware in a shared location and reference it from your API routes.
@fanoutio/serve-grip/node
exports a class constructor named ServeGrip
. This constructor takes a
configuration object that can be used to configure the instance, such as the GRIP proxies to use
for publishing or whether incoming requests should require a GRIP proxy.
The following is an example of configuration when the GRIP proxy is an instance of Pushpin running on localhost:
import { ServeGrip } from '@fanoutio/serve-grip/node';
const serveGripMiddleware = new ServeGrip({
grip: {
control_uri: 'https://localhost:5561/', // Control URI for Pushpin publisher
control_iss: '<issuer>', // (opt.) iss needed for publishing, if required by Pushpin
key: '<publish-key>', // (opt.) key needed for publishing, if required by Pushpin
},
isGripProxyRequired: true,
});
The following is an example of configuration when the GRIP proxy is Fastly Fanout:
import { ServeGrip } from '@fanoutio/serve-grip/node';
const serveGripMiddleware = new ServeGrip({
grip: {
control_uri: 'https://api.fastly.com/service/<service-id>/', // Control URI
key: '<fastly-api-token>', // Authorization key for publishing (Fastly API Token)
verify_iss: 'fastly:<service-id>', // Fastly issuer used for validating Grip-Sig
verify_key: '<verify-key>', // Fastly public key used for validating Grip-Sig
},
isGripProxyRequired: true,
});
Often the configuration is done using a GRIP_URL
(and if needed, GRIP_VERIFY_KEY
), allowing for configuration using simple strings. This allows for configuration from environment variables:
GRIP_URL="https://api.fastly.com/service/<service-id>/?verify-iss=fastly:<service-id>&key=<fastly-api-token>"
GRIP_VERIFY_KEY="base64:LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQ0tvNUExZWJ5RmNubVZWOFNFNU9uKzhHODFKeQpCalN2Y3J4NFZMZXRXQ2p1REFtcHBUbzN4TS96ejc2M0NPVENnSGZwLzZsUGRDeVlqanFjK0dNN3N3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t"
import { ServeGrip } from '@fanoutio/serve-grip/node';
const serveGripMiddleware = new ServeGrip({
grip: process.env.GRIP_URL,
gripVerifyKey: process.env.GRIP_VERIFY_KEY,
isGripProxyRequired: true,
});
Note
When used with Hono, @fanoutio/serve-grip/hono
exports a function named serveGrip
rather than a constructor.
This function takes the same object as the parameter to the ServeGrip
constructor described above, or a promise that
resolves to such an object. It can also be called with a callback that returns such an object or promise.
This is useful for environments such as Fastly Compute, where runtime configuration may not be available until
request processing.
Example:
import { serveGrip } from '@fanoutio/serve-grip/hono';
const serveGripMiddleware = serveGrip(async () => {
const gripUrl = await loadGripUrl();
return {
grip: gripUrl,
};
});
Available options:
Key | Value |
---|---|
grip |
A definition of GRIP proxies used to publish messages, or a preconfigured Publisher object from @fanoutio/grip . See below for details. |
gripVerifyKey |
(optional) A string or Buffer that can be used to specify the verify-key component of the GRIP configuration.Applies only if - * grip is provided as a string, configuration object, or array of configuration objects* grip does not already contain a verify_key value. |
gripProxyRequired |
(optional) A boolean value representing whether all incoming requests should require that they be called behind a GRIP proxy. If this is true and a GRIP proxy is not detected, then a 501 Not Implemented error will be issued. Defaults to false . |
prefix |
(optional) A string that will be prepended to the name of channels being published to. This can be used for namespacing. Defaults to '' . |
In most cases your application will construct a singleton instance of this class and use it as the middleware.
The grip
parameter may be provided as any of the following:
-
An object with the following fields:
Field Description control_uri
The Control URI of the GRIP client. control_iss
(optional) The Control ISS, if required by the GRIP client. key
(optional) string or Buffer. The key to use with the Control ISS, if required by the GRIP client. verify_iss
(optional) The ISS to use when validating a GRIP signature. verify_key
(optional) string or Buffer. The key to use when validating a GRIP signature. -
An array of such objects.
-
A GRIP URI, which is a string that encodes the above as a single string.
-
(advanced) A
Publisher
object that you have instantiated and configured yourself, from@fanoutio/grip
.
After the middleware has run, your handler will receive the Grip context grip
(On req
and res
objects or on the Hono
context c
). These provide access to the following:
Express, Koa, etc. | Hono | Description |
---|---|---|
req.grip.isProxied |
c.var.grip.isProxied |
A boolean value indicating whether the current request has been called via a GRIP proxy. |
req.grip.isSigned |
c.var.grip.isSigned |
A boolean value indicating whether the current request is a signed request called via a GRIP proxy. |
req.grip.wsContext |
c.var.grip.wsContext |
If the current request has been made through WebSocket-Over-HTTP, then a WebSocketContext object for the current request. See @fanoutio/grip for details on WebSocketContext . |
res.grip.startInstruct() |
c.var.grip.startInstruct() |
Returns an instance of GripInstruct , which can be used to issue instructions to the GRIP proxy to hold connections. See @fanoutio/grip for details on GripInstruct . |
To publish messages, obtain a Publisher
. Use it to publish messages using the endpoints and prefix specified when the middleware was initialized.
Express, Koa, etc. | Hono | Description |
---|---|---|
serveGripMiddleware.getPublisher() |
c.var.grip.getPublisher() |
Returns an instance of Publisher , which can be used to publish messages to the provided publishing endpoints using the provided prefix. See @fanoutio/grip for details on Publisher . |
This repository contains examples to illustrate the use of serve-grip
in Connect / Express
and Next.js, which can be found in the examples
directory. For details on each example, please
read the README.md
files in the corresponding directories.
Fastly Compute is an advanced edge computing platform that runs code (compiled to WebAssembly) on Fastly's global edge network.
When using Fastly Compute, a single application can both initiate a GRIP handoff and process proxied GRIP requests by configuring the application itself as the backend. In this setup, the app runs twice per request:
- once as the outer Compute app, which activates the GRIP proxy, and
- once again as the inner proxied app, invoked through the GRIP proxy.
To support this pattern, @fanoutio/serve-grip/hono
provides an additional Hono middleware,
fanoutSelfHandoffMiddleware
, which is included automatically when running under the Fastly Compute JavaScript
SDK (via the fastly
conditional runtime key).
import { fanoutSelfHandoffMiddleware } from '@fanoutio/serve-grip/hono';
app.get('/api/*', fanoutSelfHandoffMiddleware('self'));
This middleware inspects whether the app is running in the outer Compute context or the inner proxied context:
- In the outer context, it performs a Fanout handoff back to
'self'
, which must be defined as a backend in your Fastly service that points to itself. - In the inner context, it allows the request to continue as usual.
Note
The examples in this repository demonstrate this configuration with Fastly Fanout local testing:
When deploying to your Fastly account, ensure that:
- Fanout is enabled on your service, and
- A backend pointing to itself is configured.
For more details, see Deploy to a Fastly Service in the Fastly documentation.
(C) 2015, 2020 Fanout, Inc.
(C) 2025 Fastly, Inc.
Licensed under the MIT License, see file LICENSE.md for details.