Autonomous development
Autonomous development
By adopting a simple manifest, teams can develop and serve parts of a web page in isolation as if one where developing and hosting a full site. These isolated parts, aka Podlets, can easily be developed in any technology stack or one can opt in to using the node.js Podium library with your favorite HTTP framework of choice.
Powerful composition
Powerful composition
Podium makes it easy, yet flexible, to compose parts developed in isolation into full complex pages, aka Layouts. Page compostion is done programmatically instead of through config or markup providing much more power and freedom to make compostion suit every need one might have.
Contract based
Contract based
Composition with Podium is done over HTTP but between the isolated parts and the composition layer there is a strong contract. This ensures that the isolated parts always has a set of key, request bound, properties which can be of value to them to operate while the composition layer has usefull info about each isolated part it can use when composing.
Podlets
Podlets
Podlets (page fragments) are standalone HTTP services developed and run in isolation. Podlets can be written in any language, but Podium comes with a @podium/podlet module for easy development of Podlets in node.js.
This is a Podlet serving a HTML endpoint responding to the locale it get from a Layout:
const express = require('express');
const Podlet = require('@podium/podlet');
const app = express();
const podlet = new Podlet({
name: 'myPodlet',
version: '1.0.0',
pathname: '/',
development: true,
});
app.use(podlet.middleware());
app.get(podlet.content(), (req, res) => {
if (res.locals.podium.context.locale === 'nb-NO') {
return res.status(200).podiumSend('<h2>Hei verden</h2>');
}
res.status(200).podiumSend(`<h2>Hello world</h2>`);
});
app.get(podlet.manifest(), (req, res) => {
res.status(200).json(podlet);
});
app.listen(7100);
const HapiPodlet = require('@podium/hapi-podlet');
const Podlet = require('@podium/podlet');
const Hapi = require('hapi');
const app = Hapi.Server({
host: 'localhost',
port: 7100,
});
const podlet = new Podlet({
name: 'myPodlet',
version: '1.0.0',
pathname: '/',
development: true,
});
app.register({
plugin: new HapiPodlet(),
options: podlet,
});
app.route({
method: 'GET',
path: podlet.content(),
handler: (request, h) => {
if (request.app.podium.context.locale === 'nb-NO') {
return h.podiumSend('<h2>Hei verden</h2>');
}
return h.podiumSend('<h2>Hello world</h2>');
},
});
app.route({
method: 'GET',
path: podlet.manifest(),
handler: (request, h) => JSON.stringify(podlet),
});
app.start();
const fastifyPodlet = require('@podium/fastify-podlet');
const fastify = require('fastify');
const Podlet = require('@podium/podlet');
const app = fastify();
const podlet = new Podlet({
name: 'myPodlet',
version: '1.0.0',
pathname: '/',
development: true,
});
app.register(fastifyPodlet, podlet);
app.get(podlet.content(), async (request, reply) => {
if (reply.app.podium.context.locale === 'nb-NO') {
reply.podiumSend('<h2>Hei verden</h2>');
return;
}
reply.podiumSend('<h2>Hello world</h2>');
});
app.get(podlet.manifest(), async (request, reply) => {
reply.send(podlet);
});
const start = async () => {
try {
await app.listen(7100);
app.log.info(`server listening on ${app.server.address().port}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
const { HttpIncoming } = require('@podium/utils');
const Podlet = require('@podium/podlet');
const http = require('http');
const podlet = new Podlet({
name: 'myPodlet',
version: '1.0.0',
pathname: '/',
development: true,
});
const server = http.createServer(async (req, res) => {
let incoming = new HttpIncoming(req, res);
incoming = await podlet.process(incoming);
if (incoming.url.pathname === podlet.manifest()) {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.setHeader('podlet-version', podlet.version);
res.end(JSON.stringify(podlet));
return;
}
if (incoming.url.pathname === podlet.content()) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.setHeader('podlet-version', podlet.version);
if (incoming.context.locale === 'nb-NO') {
res.end(podlet.render(incoming, '<h2>Hei verden</h2>'));
return;
}
res.end(podlet.render(incoming, '<h2>Hello world</h2>'));
return;
}
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not found');
});
server.listen(7100);
Start the Podlet and it will be accessable on http://localhost:7100/
Layouts
Layouts
Layouts are standalone HTTP services which on run time compose one or multiple Podlets (page fragments) into full webpages. Layouts are written in node.js with the @podium/layout module.
This is a Layout which will include two Podlets:
const express = require('express');
const Layout = require('@podium/layout');
const layout = new Layout({
name: 'myLayout',
pathname: '/',
});
const podletA = layout.client.register({
name: 'myPodletA',
uri: 'http://localhost:7100/manifest.json',
});
const podletB = layout.client.register({
name: 'myPodletB',
uri: 'http://localhost:7200/manifest.json',
});
const app = express();
app.use(layout.middleware());
app.get(layout.pathname(), async (req, res, next) => {
const incoming = res.locals.podium;
const [a, b] = await Promise.all([
podletA.fetch(incoming),
podletB.fetch(incoming),
]);
res.podiumSend(`
<section>${a.content}</section>
<section>${b.content}</section>
`);
});
app.listen(7000);
const HapiLayout = require('@podium/hapi-layout');
const Layout = require('@podium/layout');
const Hapi = require('hapi');
const app = Hapi.Server({
host: 'localhost',
port: 7000,
});
const layout = new Layout({
name: 'myLayout',
pathname: '/',
});
const podletA = layout.client.register({
name: 'myPodletA',
uri: 'http://localhost:7100/manifest.json',
});
const podletB = layout.client.register({
name: 'myPodletB',
uri: 'http://localhost:7200/manifest.json',
});
app.register({
plugin: new HapiLayout(),
options: layout,
});
app.route({
method: 'GET',
path: layout.pathname(),
handler: (request, h) => {
const incoming = request.app.podium;
const [a, b] = await Promise.all([
podletA.fetch(incoming),
podletB.fetch(incoming),
]);
h.podiumSend(`
<section>${a.content}</section>
<section>${b.content}</section>
`);
},
});
app.start();
const fastifyLayout = require('@podium/fastify-layout');
const fastify = require('fastify');
const Layout = require('@podium/layout');
const app = fastify();
const layout = new Layout({
name: 'myLayout',
pathname: '/',
});
const podletA = layout.client.register({
name: 'myPodletA',
uri: 'http://localhost:7100/manifest.json',
});
const podletB = layout.client.register({
name: 'myPodletB',
uri: 'http://localhost:7200/manifest.json',
});
app.register(fastifyLayout, layout);
app.get(layout.pathname(), async (request, reply) => {
const incoming = reply.app.podium;
const [a, b] = await Promise.all([
podletA.fetch(incoming),
podletB.fetch(incoming),
]);
reply.podiumSend(`
<section>${a.content}</section>
<section>${b.content}</section>
`);
});
const start = async () => {
try {
await app.listen(7000);
app.log.info(`server listening on ${app.server.address().port}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
start();
const { HttpIncoming } = require('@podium/utils');
const Layout = require('@podium/layout');
const http = require('http');
const layout = new Layout({
name: 'myLayout',
pathname: '/',
});
const podletA = layout.client.register({
name: 'myPodletA',
uri: 'http://localhost:7100/manifest.json',
});
const podletB = layout.client.register({
name: 'myPodletB',
uri: 'http://localhost:7200/manifest.json',
});
const server = http.createServer(async (req, res) => {
let incoming = new HttpIncoming(req, res);
incoming = await layout.process(incoming);
if (incoming.url.pathname === layout.pathname()) {
const [a, b] = await Promise.all([
podletA.fetch(incoming),
podletB.fetch(incoming),
]);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(podlet.render(incoming, `
<section>${a.content}</section>
<section>${b.content}</section>
`));
return;
}
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not found');
});
server.listen(7000);
Start the Layout and it will be accessable on http://localhost:7000/