Server
If your GraphQL API is served from a Node.js server, rendering Ruru alongside it enables your users to explore and query your API with minimal fuss.
Ruru is unopinionated on servers, giving you two methods that should work with any server:
ruruHTML(...)
- generate the HTML filegetStaticFile(...)
- return the buffer and headers for the given static file, if it exists
If you're using node, express, or a similar server then you can use the
serveStatic(...)
middleware to handle the getStaticFile(...)
calls for you.
You only need to serve the HTML (using the ruruHTML(...)
helper) - the assets
will be served from unpkg.com
by default.
However it's advised that you do serve the static assets (e.g. using
serveStatic(...)
) as this allows your development environment to run Ruru even
when there is no internet connection.
Example: express
import express from "express";
import { ruruHTML } from "ruru/server";
import { serveStatic } from "ruru/static";
const app = express();
const config = { staticPath: "/ruru-static/", endpoint: "/graphql" };
// Serve Ruru HTML
app.get("/", (req, res) => {
res.format({
html: () => res.status(200).send(ruruHTML(config)),
default: () => res.status(406).send("Not Acceptable"),
});
});
// Serve static files
app.use(serveStatic(config.staticPath));
// Don't forget to serve your GraphQL endpoint:
//
// app.post("/graphql", ...);
Example: node
import { createServer } from "node:http";
import { ruruHTML } from "ruru/server";
import { serveStatic } from "ruru/static";
const config = {
staticPath: "/ruru-static/",
endpoint: "https://example.com/graphql", // Your endpoint
};
// Create the static middleware _ONCE ONLY_ for efficiency.
const staticMiddleware = serveStatic(config.staticPath);
// Create your HTTP server
const server = createServer((req, res) => {
if (req.url === "/") {
// Serve Ruru HTML
const html = ruruHTML(config);
res.writeHead(200, {
"content-type": "text/html",
"content-length": html.length,
});
res.end(html);
return;
} else {
// Serve static files
return staticMiddleware(req, res);
}
});
config
No matter which server you're using you'll need a Ruru config object which should contain at least the following:
staticPath
: the URL path on your server from which static assets should be served - this must start and end with a slashendpoint
: where to find the GraphQL endpoint - an absolute URL starting either with/
for same-domain, orhttp://
/https://
.
ruruHTML(config)
import { ruruHTML } from "ruru/server";
const html = ruruHTML(config);
This function will return you the HTML to render to the user when they request the Ruru endpoint; serve it as HTML as you normally would. Use it at whatever URL you wish to render Ruru.
serveStatic(staticPath)
import { serveStatic } from "ruru/static";
const staticMiddleware = serveStatic(config.staticPath);
This will return a middleware compatible with Node, Express, Connect and similar
servers to serve the static files that Ruru depends on. You must pass it the
same staticPath
that you passed to ruruHTML(config)
.
getStaticFile(context)
import { getStaticFile } from "ruru/static";
const file = await getStaticFile({
staticPath: config.staticPath,
urlPath: request.url,
acceptEncoding: request.headers["accept-encoding"],
});
The serveStatic
middleware works for Express, Connect, Node and some others,
but if you're not using these then we give you a simple function you can use to
serve the required static file from any Node.js based server framework.
Context is an object with the following values:
staticPath
- the samestaticPath
as found inconfig.staticPath
passed toruruHTML
aboveurlPath
- the path the current HTTP request requested (must start with/
, can include query string)acceptEncoding
- the value of theAccept-Encoding
HTTP header, orundefined
if not setdisallowDevAssets
(optional) - set totrue
to forbid sending source maps (reduces server memory pressure by about 10MB at the cost of debuggability)
The result is either null
if the file isn't found, or an object containing:
headers
- a headers object to set on the response (Content-Type, Content-Length, Content-Encoding, etc)content
- a buffer containing the data to serve
Use your framework to serve this as appropriate:
// This is an **IMAGINARY** server API, adjust to fit your server.
if (file) {
// Found!
const { etag } = file.headers;
if (request.headers["if-none-match"] === etag) {
response.status(304).headers({ etag }).send(); // Don't serve; etag matched!
} else {
response.status(200).headers(file.headers).send(file.content);
}
} else {
response.status(404).send("Not Found");
}