Skip to content

Instantly share code, notes, and snippets.

@wajeht
Forked from crgeary/inertia.js
Created January 21, 2023 01:06
Show Gist options
  • Save wajeht/58396a24cd90c113d2d9bad874695f91 to your computer and use it in GitHub Desktop.
Save wajeht/58396a24cd90c113d2d9bad874695f91 to your computer and use it in GitHub Desktop.
Inertia adapter for Express
const lodashPick = (object, keys) => {
return keys.reduce((obj, key) => {
if (object && object.hasOwnProperty(key)) {
obj[key] = object[key];
}
return obj;
}, {});
};
const setupProps = async (props) => {
const _props = { ...props };
for (i in _props) {
if (typeof _props[i] === "object") {
_props[i] = await setupProps(_props[i]);
} else if (typeof _props[i] === "function") {
_props[i] = await _props[i].call();
}
}
return _props;
};
const getRequestedProps = (req, component, props) => {
const requestedProps = req.header("X-Inertia-Partial-Data");
if (requestedProps) {
if (req.header("X-Inertia-Partial-Component") === component) {
return lodashPick(props, requestedProps.split(","));
}
}
return props;
};
const shouldSeeOther = (req, res) => {
const methods = ["PUT", "PATCH", "DELETE"];
return res.statusCode === 303 && methods.includes(req.method);
};
const shouldConflict = (req, version) => {
return req.method === `GET` && req.header("X-Inertia-Version") !== version;
};
const inertia = (options = {}) => {
options = {
version: null,
view: "app",
...options,
};
return (req, res, next) => {
res.inertia = async (component, inertiaProps, viewProps) => {
const url = `${req.protocol}://${req.get("host")}${
req.originalUrl
}`;
const props = getRequestedProps(req, component, inertiaProps);
const inertiaObject = {
component: component,
props: await setupProps(props),
url: url,
version: options.version,
};
if (!req.header("X-Inertia")) {
return res.render(options.view, {
layout: false,
page: JSON.stringify(inertiaObject),
...viewProps,
});
}
if (shouldConflict(req, options.version)) {
if (req.session && typeof req.flash === "function") {
const messages = req.flash();
for (message in messages) {
req.flash(message, messages[message]);
}
}
res.setHeader("X-Inertia-Location", url);
return res.status(409).end();
}
res.setHeader("Vary", "Accept");
res.setHeader("X-Inertia", "true");
res.setHeader("X-Inertia-Version", options.version);
return res.json(inertiaObject);
};
const end = res.end;
res.end = function () {
if (shouldSeeOther(req, res)) {
res.status(303);
}
return end.apply(this, arguments);
};
next();
};
};
exports.inertia = inertia;
const express = require("express");
const session = require("express-session");
const flash = require("connect-flash");
const { inertia } = require("./inertia");
const app = express();
app.engine("hbs", hbs.engine);
app.set("view engine", "hbs");
app.use(
session({
secret: "@todo assign a secret",
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 15,
},
})
);
app.use(flash());
app.use(
inertia({
view: "app",
version: "@todo webpack manifest hash (for example)",
})
);
app.get("/", async (req, res) => {
res.inertia("Dashboard", {
title: "Dashboard!",
reports: async () => {
return await aTimeConsumingTasksLikeDatabaseAccess();
}
});
});
app.listen(3000, () => {
console.log("Listening at http://localhost:3000");
});
<!DOCTYPE html>
<html lang="en-gb">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>App</title>
</head>
<body>
<div id="app" data-page="{{ page }}"></div>
<script src="/app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment