Building an RSS Microservice with Deno and GraphQL
Tags: tutorial, deno, graphql, typescript
Published: 2020-11-09 22:27:57

# 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](https://deno.land/). 

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](https://dev-to-uploads.s3.amazonaws.com/i/sjinta0v99ohhe2fgczg.jpg)!

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](https://dev-to-uploads.s3.amazonaws.com/i/8j9feh9funyk8jzff9is.png)

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
```



## 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](https://dev-to-uploads.s3.amazonaws.com/i/qlxnm2cm3vbqdnh4yqgd.png)

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

```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"
  }
}
```



## Import Oak and initialize app

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

const app = new Application()
```
We are going to use [Oak](https://github.com/oakserver/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](https://api.spacex.land/graphql/) so that you can follow along.

First define the query we want from the API.

```ts
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
    }
  }
}
`
```

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

``` ts
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()
})
```

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](https://validator.w3.org/feed/docs/rss2.html) 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](https://deno.land/std@0.74.0/node) 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](https://jspm.dev) and [skypack.dev](https://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](https://github.com/jpmonette/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

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

Now in our middleware we can create the Feed object


```ts
// ...

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()
  })
})
```

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

```ts
// ...

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)
  })
})
```

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](https://graphql-code-generator.com/).

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

```ts
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()
})
```

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

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

app.listen({ port: 3000 })
```

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.