DEV Community

Cover image for Duck duck go example
Bryan Ollendyke
Bryan Ollendyke

Posted on • Edited on

Duck duck go example

The first example I made to start unpacking how I'd manage these at scale was a basic call to the Duck Duck Go API. When drafting a micro-frontend it's important to consider the following:

  • Is this reusable in other projects or a one off? Reuse is great, but will increase API complexity as you envision other use-cases vs things that are only internal to the site in question.
  • Is this visual or a non visual implementation? Visual should be made out of several non-visual implementations to avoid coupling too tightly.
  • Is this taking user input or answering a machine question? Do we need to satisfy the user's request or is this more so a button they click and the current page does data churn to form the answer.
  • Is this a critical service or a nice-to-have? Critical services (login button) or nice to have (PDF this page), either way there must be a fallback plan in the event the micro-service doesn't respond.

Duck Duck Go

Let's start on the front end implementation and work our way back to the backend. This is very example driven, so it's just a basic form input

<div>
        <label>Duck duck go</label>
        <input type="text" id="search" />
        <button id="searchbtn">Search</button>
        <div id="ddgresult"></div>
      </div>
Enter fullscreen mode Exit fullscreen mode

After the user inputs the search term, we capture via click event on the search button

      this.shadowRoot
        .querySelector("#searchbtn")
        .addEventListener("click", () => {
          const params = {
            q: this.shadowRoot.querySelector("#search").value,
          };
          MicroFrontendRegistry.call("@core/duckDuckGo", params, this.ddgCallback.bind(this));
        });
Enter fullscreen mode Exit fullscreen mode

and then call our abstraction / frontend middleware class called MicroFrontendRegistry. I'll write another post on how I went about constructing the API for this, but the idea is that instead of calling an endpoint, we want to call a developer name for the function and let the environment figure out the actual full address.

After the q param is sent up to the microservice, the ddgCallback is executed with the response data from the end point.

The microservice

Vercel handles all the deployment magic as well as assuming certain configuration settings associated with express. Combined with some lessons learned (covered in the backend API structure) we get a very clean endpoint for handling our DDG request.

// duckduckgo.js
// this is an example to fork from that uses common, simple conventions
// for getting data, validating data, and responding in a consistent way.
import { stdPostBody, stdResponse, invalidRequest } from "../../../utilities/requestHelpers.js";
import fetch from "node-fetch";
export default async function handler(req, res) {
  // destructing GET params after ? available in this object
  var { q } = req.query;
  // use this if POST data is what's being sent
  const body = stdPostBody(req);
  // fallback support for post
  if (!q && body && body.q) {
    q = body.q;
  }
  // need to know what we're searching for otherwise bail
  if (q) {
    // we import fetch just to simplify endpoint creation but its just node-fetch
    const searchResults = await fetch(`http://api.duckduckgo.com/?q=${q}&format=json`).then((d) => d.ok ? d.json(): {});
    // standard response is how all transactions end
    // this will assume 200 response code unless defined otherwise
    // response data is passed in as searchResults here
    // if type is not set in the options, then it is assumed JSON response
    // and is added to a data param
    res = stdResponse(res, searchResults, {cache: 86400, methods: "OPTIONS, POST" });
  }
  else {
    // invalidate the response and provide a reason
    // this optionally takes in a status code otherwise default is 400
    // vercel will through a 500 if there was any bricking issue so we don't
    // need to throw that most likely
    res = invalidRequest(res, 'missing `q` param');
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we can see we pull in some simple validation of input before sending off to the duck duck go API. stdResponse then takes JSON data and just sets the appropriate response before sending back to the front end. The above is well documented so read through what's going on there but it's pretty simple with the minor abstractions to working with res and req.

Showing results time

So once the backend sends this JSON blob back, the frontend's Promise will be resolved in the ddgCallback and run with the data that was returned.

ddgCallback(data) {
    console.log(data.data.RelatedTopics);
    this.shadowRoot.querySelector("#ddgresult").innerHTML = 
    `<code><pre>
    ${JSON.stringify(data.data.RelatedTopics, null, 4)}
    </pre></code>`;
  }
Enter fullscreen mode Exit fullscreen mode

Here we can see it's a simple printing out the stringified version to the page.

This is the rather simple pattern that emerges with microfrontends from my building several out.

  • Have input
  • make call in standard way (Promise so async/await as desired)
  • get call in standard way, return data in standard way (json w/ data param)
  • display / react to input

Now that we've got a simple example, let's step into the front-end abstraction that I wrote to make those calling of web services boil down to single line calls.

Top comments (0)