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.
Among many other awesome features, Deno promises to do the impossible: get rid of the heaviest object in the universe, the node_modules
folder.
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
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"
}
}
Import Oak and initialize app
import { Application } from 'https://deno.land/x/oak/mod.ts'
const app = new Application()
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
}
}
}
`
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()
})
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'
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()
})
})
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)
})
})
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()
})
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 })
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.
Top comments (3)
after running this:
Opening the endpoint in firefox gets me:
dev-to-uploads.s3.amazonaws.com/i/...
Any idea what that is about? Reproduction: github.com/idkjs/rss-microservice
Thanks for sharing this. Deno has been edging into my space.
I'm not a firefox user but I believe this means things are working normally! The endpoint should return an XML document. I think firefox believes this is a file and is trying to download it. In Chromium visiting the endpoint shows the markup text
Typos:
deno run --allow-net src/index.ts
ctx.response.body = myFeed.rss2()