Building an RSS Microservice with Deno and GraphQL

seanaye

Sean Aye

Posted on November 9, 2020

Building an RSS Microservice with Deno and GraphQL

Intro

2020 has been awful in pretty much every way imaginable. Off the top of my head, one of the only promising things that happened this year was the 1.0 release of Deno.

Deno is the successor (hopefully) to Node.js and it brings a lot more to the table than just an objectively better logo.

Alt Text!

Among many other awesome features, Deno promises to do the impossible: get rid of the heaviest object in the universe, the node_modules folder.

Alt Text

I, personally, cannot wait for this to happen and therefore I feel morally obligated to spread the good word of Deno in the form of a tutorial.

Tut

Install Deno

most operating systems
$ curl -fsSL https://deno.land/x/install/install.sh | sh

windows
$ iwr https://deno.land/x/install/install.ps1 -useb | iex

Check deno is installed properly
$ deno --version

deno 1.5.1
v8 8.7.220.3
typescript 4.0.3
Enter fullscreen mode Exit fullscreen mode

Create Project Folder

$ mkdir rss-microservice
$ cd rss-microservice
$ mkdir src
$ touch src/index.ts

Setup VS Code for Deno (Optional)

Install the Deno VS Code extension

Alt Text

Add the following config to your project .vscode/settings.json

{
  "deno.enable": true,
  "deno.import_map": "./path/to/import_map.json",
  "deno.unstable": true,
  "deno.lint": true,
  "deno.import_intellisense_origins": {
    "https://deno.land": true,
  },
  "deno.import_intellisense_autodiscovery": true,
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}
Enter fullscreen mode Exit fullscreen mode

Import Oak and initialize app

import { Application } from 'https://deno.land/x/oak/mod.ts'

const app = new Application()
Enter fullscreen mode Exit fullscreen mode

We are going to use Oak which is a popular middleware framework for Deno inspired by Koa. It's also a type of tree.

Each time the microservice receives a request we want to respond with data in rss compatible xml. First we need to fetch our data from GraphQL. Here we will use the public Space X GraphQL API so that you can follow along.

First define the query we want from the API.

import { Application } from 'https://deno.land/x/oak/mod.ts'

const app = new Application()

const spaceXApi = 'https://api.spacex.land/graphql/'

const query = `#graphql
{
  launchesPast(sort: "desc" limit: 20) {
    mission_name
    launch_date_local
    links {
      article_link
      video_link
    }
    rocket {
      rocket_name
    }
  }
}
`
Enter fullscreen mode Exit fullscreen mode

We want to fetch new data on every request so let's put the call to fetch inside a middleware.

const query = `#graphql
...
`

app.use(async (ctx, next) => {
  const res = await fetch(spaceXApi, {
    body: JSON.stringify({ query }),
    method: 'POST',
    headers: { 'Content-Type': 'application/json' }
  })
  const data = await res.json()
})
Enter fullscreen mode Exit fullscreen mode

GraphQL is really that easy! We don't need any external dependencies since fetch is built right into Deno. We don't need no fancy shmancy Apollo Client here either, that would definitely be overkill.

How do we turn this into an RSS feed?

A tangent about Deno
Feel free to skip to next section

Great question. For the zoomers out there, RSS is something that came after the milkman but before TikTok and it is basically just an XML document served over HTTP that has a spec it must adhere to in order for it to be considered a valid RSS feed.

We need some way of turning our JSON data into XML data which conforms to the RSS spec. We could do this from scratch but that would involve a bunch of reading spec documents and templating our data into XML which sounds like really dry tutorial material.

In the node.js world we could probably just find a module for this, but here in deno.land we don't have the luxury of about a bajillion modules at our disposal... or do we?

Kind of... probably only about half a bajillion

See, Deno doesn't support the old const x = require('x') syntax from node.js, instead it prefers the newer and better es module syntax with import x from 'y'. And while there is a standard module for node compatibility we should avoid it whenever possible because it is un-deno-like and it brings back the dreaded node_modules folder.

Rumour has it there are some new CDN's on the block that automagically take npm packages and convert them to the new es import syntax and bundle all the required dependencies. jspm.dev and skypack.dev are the two I'm aware of.

This is not a silver bullet to import any node module but it certainly works for feed which is an RSS and Atom feed generator for Node.js

End tangent let's get back to the code.

Import Feed Package

Import the feed module from jspm at the top of our file

import { Application } from 'https://deno.land/x/oak/mod.ts'
import { Feed } from 'https://jspm.dev/feed'
Enter fullscreen mode Exit fullscreen mode

Now in our middleware we can create the Feed object

// ...

app.use(async (ctx, next) => {
  const res = await fetch(spaceXApi, {
    body: JSON.stringify({ query }),
    method: 'POST'
  })
  const data = await res.json()
  const myFeed = new Feed({
    title: 'The Most Average Space X RSS Feed ™',
    description: 'A feed of stuff that Space X be doin',
    link: 'https://spaceflightnow.com',
    updated: new Date()
  })
})
Enter fullscreen mode Exit fullscreen mode

This creates the main part of the feed object but now we want to add each of the SpaceX launches as an item

// ...

const myFeed = new Feed({
  // ...
})

data.data.launchesPast.forEach((launch: any) => {
  myFeed.addItem({
    title: launch.mission_name,
    id: launch.links.article_link,
    link: launch.links.article_link || launch.links.video_link,
    description: `Smart people put a ${launch.rocket.rocket_name} in space`,
    date: new Date(launch.launch_date_local)
  })
})
Enter fullscreen mode Exit fullscreen mode

Yes I'm being naughty and using an any type, Deno complains about implicit any types and I don't feel like creating an interface for the response data. A great way to do this for larger projects is to automatically generate typescript types for all your queries with GraphQL Codegen.

Ok now we have all our data in the feed object. Let's output an XML string and send it as a response.

app.use(async (ctx, next) => {
  // ...
  ctx.response.body = myFeed.rss2()
  // we need to set the MIME type for this to be valid RSS
  ctx.response.headers.set('Content-Type', 'application/rss+xml')
  await next()
})
Enter fullscreen mode Exit fullscreen mode

That's it! We can listen for errors in the app and run it locally with

app.addEventListener('error', e => {
  console.log('Error: ', e.error)
})

app.listen({ port: 3000 })
Enter fullscreen mode Exit fullscreen mode

Let's run the app

$ deno run --allow-net src/index.ts

And open localhost:3000 in a browser.
Tada, your very own RSS microservice using Deno, Typescript and GraphQL.

💖 💪 🙅 🚩
seanaye
Sean Aye

Posted on November 9, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related