
Podium.io
Easy server side composition of microfrontends
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
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
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.
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:
- Express
- Hapi
- Fastify
- Http
import express from 'express';
import Layout from '@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);
import HapiLayout from '@podium/hapi-layout';
import Layout from '@podium/layout';
import Hapi from '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();
import fastifyLayout from '@podium/fastify-layout';
import fastify from 'fastify';
import Layout from '@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();
import { HttpIncoming } from '@podium/utils';
import Layout from '@podium/layout';
import http from '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/
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:
- Express
- Hapi
- Fastify
- Http
import express from 'express';
import Layout from '@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);
import HapiLayout from '@podium/hapi-layout';
import Layout from '@podium/layout';
import Hapi from '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();
import fastifyLayout from '@podium/fastify-layout';
import fastify from 'fastify';
import Layout from '@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();
import { HttpIncoming } from '@podium/utils';
import Layout from '@podium/layout';
import http from '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/