Fastify render view with Amedia Component

Safet Zahirovic (@zahsaf17)

Fastify tutorial in 4 parts

First, we need to create our views. We're going to focus on creating views rendered with Amedia Component. We're not going to svelte in Amedia Component in this tutorial, but you can read about it here https://github.com/amedia/amedia-component.

Now, let's start by making our view. Create a new folder called views and create a new file called example.view.js. There, write in the following code:

export function renderView({ name, manifest }) {
  return `
    <!doctype html>
        <html>
        <head>
            <title>Hello</title>
            <script
            type="module"
            src="https://assets.acdn.no/pkg/@amedia/include/v3/include.js"
            async
            crossorigin
          ></script>
        </head>
        <body>
            <amedia-include manifest="${manifest}" param-name=${name}></amedia-include>
        </body>
        </html>
    `;
}

This exports a function that we render our view. Look at this attribute, src="https://assets.acdn.no/pkg/@amedia/include/v3/include.js". Here we are importing Amedia Component script, which will render our manifest and find where all artifacts are stored and execute them with this tag <amedia-include manifest="${manifest}" param-name=${name}></amedia-include>.

Now with that out of the way, let's create our manifest. Create a new folder called manifests and create a file called example.manifst.js. We're not going to go deep about what manifest does, but essentially, it reads where all the artifacts (css, js files) are stored, and tells the browser what to render with Amedia Component.

const ORIGIN = 'localhost:3000';

const manifestHost = `http://${ORIGIN}`;

const js = [`${manifestHost}/assets/index.js`];

const css = [`${manifestHost}/assets/styles.css`];

export const example = {
  id: 'TEMPLATE_APPNAME',
  version: '0.1.0',
  name: 'Template Name',
  title: 'Template Title',
  description: 'Template Description',
  platform: ['web'],
  tags: ['example', 'template'],
  urlTemplate: `${manifestHost}/example/embed`,
  queryParameters: [
    {
      name: 'name',
      description: 'Name of the person to greet',
      example: 'Geeks',
      optional: false,
    },
  ],
  js,
  css,
};

We need to wrap this in a @amedia/component-manifest. Go ahead and install @amedia/component-manifest package, and create a new file called manifests.js in manifests folder. Inside write the following code:

import { createManifest } from "@amedia/component-manifest";

// Import manifests
import { example } from "./example.manifest.js";

// Expose the manifest from this module
export const exampleManifest = createManifest(example);

This will ensure that amedia-include tag in Amedia Component will read and parse manifest properly.

As you can see, we will serve 2 new files, index.js and styles.css. These will be server as static assets, and we need to tell our server to serve them.

First let's create a folder named public and create these two files inside that folder.

Inside index.js just write console.log("Hello from index.js") and inside styles.css just write

body {
  background-color: red;
}

When that's done we need to tell our server to serve these files. In app.js write following:

await app.register(fastifyStatic, {
  root: new URL('./public', import.meta.url),
  prefix: `/assets`,
  decorateReply: false,
});

And convert build app to be an async function, and await that in index.js.

With that out of the way, lets get back to our app.js and let's add another route:

import { exampleManifest } from '../manifests/manifests.js';

app.route({
  method: 'GET',
  url: '/example/manifest',
  schema: {
    response: {
      200: {
        description: 'Default response',
        content: {
          'application/json': {
            schema: {
              type: 'object',
            },
          },
        },
      },
    },
  },
  handler: (_, reply) => {
    reply.type('application/json').send(JSON.stringify(exampleManifest));
  },
});

This will serve our manifest. But in order to serve our view, we need to add another route that will serve our view. In app.js add following code:

app.route({
  method: 'GET',
  url: '/example',
  schema: {
    description: 'creates html view from params',
    tags: ['view', 'html'],
    querystring: {
      name: {
        type: 'string',
        description: 'Name of the person to greet',
        default: 'Geeks',
      },
    },
    response: {
      200: {
        description: 'Default response',
        content: {
          'text/html': {
            schema: {
              type: 'string',
            },
          },
        },
      },
    },
  },
  handler: (request, reply) => {
    const name = request.query.name || 'Geeks';
    const manifest = '/example/manifest';

    reply.type('text/html').send(renderView({ name, manifest }));
  },
});

And at last, we need the route that will actually return markup for our manifest. That's urlTemplate property inside our manifest. This will return the templating we need to bootstrap our view, thik of it as root node. Go in app.js and write following:

app.route({
  method: 'GET',
  url: '/example/embed',
  schema: {
    description: 'creates html view from params',
    tags: ['view', 'html'],
    response: {
      200: {
        description: 'Default response',
        content: {
          'text/html': {
            schema: {
              type: 'string',
            },
          },
        },
      },
    },
  },
  handler: (request, reply) => {
    const { name } = request.query;
    reply.send(`<h1>Hello ${name}!</h1>`);
  },
});

Now if you go to localhost:3000/example you should get a webpage that says "Hello Geeks". That's a markup from our example view.

Congrats 🥳 you have now created a view, an api route and your server. We have finally both salt and pepper in our meal. But in order to consume this meal, we need to know first is it safe to do so.

In order to check if our code is safe to consume, in the next chapter we will look into testing.

Fastify tutorial in 4 parts