This article is valid up to news-server v1.0.3 - to learn about the revised API, look out for "Making Egg Trainer 6".
Today, we're having a look at one of the microservices I mentioned in the last article: the news server. If you're familiar with a tool like cURL, you can follow along and play around with the live demo of the news server (just be aware that any abuse of my dev server will lead to it's key being changed :P).
Premise
I've made a few games in the past, such as kingdombattles.net and the first iteration of eggtrainer.com, which have great big news feeds on their front page. These feeds were updated almost daily during development (a trend that actually hurts in the long run - regular intervals are better), but they were markdown-file-based and acted through the game server itself, which led to any alterations to the feed requiring alterations to the core game itself.
At some point, I decided it might be interesting to try and implement the news feed as a separate program - a microservice - which would separate the concerns for me. I tried this with the MERN-template (which will become egg trainer soon enough), and the result was remarkable! I could now work with and on the server without worrying about breaking anything in the game, including the database.
Basics
Without further ado, try typing the following into your terminal:
curl http://dev-news.eggtrainer.com:3100/news
The tool used is curl, which you should hopefully be familiar with. It is requesting a resources from a development server hosted at dev-news.eggtrainer.com
, via the port 3100
. Then, it asks specifically for the route news
.
The main URL can be anything, depending on how you set up your DNS (I use google domains for this). However, 3100
is a setting specified by the configuration files (.env, or docker-compose.yml depending on it's release), while /news
is a route chosen to allow other possible future routes to be implemented (such as /admin
, etc.)
The result you get back from curl should be a JSON array, containing objects detailing the published articles. This is the result that the front end of the template (and potentially other programs) can process into a nice pretty news feed. As it's currently set up, only the last 10 articles will be shown. The "index" field is it's absolute index, counting from the beginning.
[
{
"index": 1,
"title": "Hello World",
"author": "Kayne Ruse",
"body": "This is the body of the article.",
"edits": 0,
"createdAt": "2021-02-04T08:46:37.000Z",
"updatedAt": "2021-02-04T08:46:37.000Z"
},
...
]
Other Options
Some other options are available for simple GET commands like the one used above:
//access the articles, starting from the beginning
curl dev-news.eggtrainer.com:3100/news/archive
//access only the metadata of the articles (everything but the body)
curl dev-news.eggtrainer.com:3100/news/titles
//access only the metadata of the articles, starting from the beginning
curl dev-news.eggtrainer.com:3100/news/archive/titles
//you can also modify the maximum number of articles returned for any of the above, using "limit"
curl dev-news.eggtrainer.com:3100/news?limit=999
//you can request a specific article using it's index (from the beginning using "archive", or just it's metadata using "title")
curl dev-news.eggtrainer.com:3100/news/1
I'll go into details of the implementation below.
Publishing
Next, we'll look at how to publish an article to the news server - it's kind of useless without this. The configuration files have a field called QUERY_KEY
, which has the value key
. This is the default publish key, and should be changed before using your own copy. But it's good enough for the demo.
We're going to use a POST request, and set some information in the message's heading. Then, we'll send a JSON object as the request to publish a new article (specified with -d
.
curl -X POST -H "Content-Type: application/json" \
http://dev-news.eggtrainer.com:3100/news \
-d '{"key":"key","title":"foo","author":"your name","body":"bar"}'
Breaking down the requested JSON looks like this:
{
"key": "key",
"title": "foo",
"author": "your name",
"body": "bar"
}
The result will look something like this: {"ok":true,"index":3}
, "index" is the absolute index of the article (from the beginning). Congrats! The GET requests from above will now show your brand new article!
Editing
So... your article is published, but you've noticed a mistake! What do you do? Thankfully, I have just the solution: PATCH
. As long as you know the absolute index of the article, you can edit that specific article via it's route like this:
curl -X PATCH -H "Content-Type: application/json" \
http://dev-news.eggtrainer.com:3100/news/3 \
-d '{"key":"key","title":"fizz","body":"buzz"}'
First, change POST
to PATCH
, then add the absolute index to the end of the route. Finally, specify the publishing key and fields you want to modify in the JSON object. Any fields you don't specify won't be affected.
If you change your mind about an edit, every change is saved to the database in a table called "revisions", but there's currently no API to revert the changes.
Deleting
You know what? That article was terrible. Let's delete it.
Much like POST
and PATCH
, DELETE
is available for specific absolute indexes. The only JSON argument it takes is the key:
curl -X DELETE -H "Content-Type: application/json" \
http://dev-news.eggtrainer.com:3100/news/3 \
-d '{"key":"key"}'
GET
, POST
, PATCH
and DELETE
. Hopefully you understand, at least roughly, how to use these to manipulate the news server. Remember, the last three require the argument "key", which currently has the value "key".
BTW, deletions are also saved to "revisions", but it's a bit clunky.
Implementation Details
What I've written so far could be an article in itself - but let's keep going; lets talk about some code. Please be aware that for this section, I'm using Nodejs, express, mariaDB and Sequelize, for the most part. I linked the repository above, but I'll link to specific files here.
The server's entry point is server/server.js
; lets have a look at it's contents (click that link). Rather than explain every line, let's skip to the relevant parts.
You'll notice cors
, which is unusual - it's used to deal with cross-origin-resource-sharing, in other words allowing eggtrainer.com
or dev.eggtrainer.com
to access and use news.eggtrainer.com
.
The database
is required but not used, simply to invoke and set up sequelize. Next, the /news
route is created, which takes in the router (we'll get to that in a moment). Finally, if any other route is accessed, the user is redirected to the server's github page. This isn't standard, it's just my way of saying "read the docs!".
It would, in theory, be fairly easy to add a new section to this, or to drop /news
into another program. Yay modularity!
next, lets have a look at server/news/index.js
. I'd like to say it was a simple router, but actually, the query
function that handles GET requests is rather complex. You can, however, see a pattern - every second route handles :id
, while the query function takes two arguments each time:
router.get('/', query(false, false));
router.get('/:id(\\d+)', query(false, false));
router.get('/archive', query(true, false));
router.get('/archive/:id(\\d+)', query(true, false));
router.get('/titles', query(false, true));
router.get('/titles/:id(\\d+)', query(false, true));
router.get('/archive/titles', query(true, true));
router.get('/archive/titles/:id(\\d+)', query(true, true));
Or breaking it down:
/ - query(false, false)
/archive - query(true, false)
/titles - query(false, true)
/archive/titles - query(true, true)
Thankfully, query's implementation is much easier to read.
It has two parts that are almost the same - the first is used when "id" is specified (the absolute index of the article). They both use titlesOnly
to omit the body and send only the metadata, and they both use ascending
to order the results from the database (you can also spot limit
being used to limit the number of articles actually returned).
Other files in this folder do pretty much what you'd expect them to. server/news/publish.js publishes an article (while handling a Sequelize bug to return the absolute index), server/news/edit.js modifies an existing record and server/news/remove.js deletes the specified record.
Front-Ends
Oh boy, this has been a hell of an article, hasn't it? Everything we've been through has been new for you, so I know it might be confusing. So... lets simplify things a little, yeah?
There's a folder in the root called front-ends
which contains a series of utilities for interacting with the news server, based on the specific framework that you want to use. (confession: at the time of writing, only react components are available, but I'm planning on adding vue components soon).
Having a quick look at the react publish component, you can see a form, with a handleSubmit
function. This component takes two props: uri
and key
, which are the URL for the news server and the publishing key respectfully. Other than that, it's ready to go. It even handles trimming the input. (There's no CSS, so feel free to customize it as you see fit.)
Similarly, the edit component has some nifty features - it uses an external package called react-dropdown-select
to create a fancy drop down, and then populates it with every article title written so far. You can select one, and it'll request the full body of that article, and fill out the form for you to conveniently edit. It also requires the uri
and key
props, but otherwise it works just as smoothly as the publish component.
There is no delete component, because uhh... I may have forgotten to write one. But I might add it as a feature of the edit component at some point...
Finally, let's talk about the star of the show - the news feed component. This one's code is fairly compact, simply fetching the most recent articles and rendering them, but in it's small size are a few tiny features, like pluralizing the word "edit" when there's more than one, or hiding that info entirely if there are 0 edits so far. it can theory handle any number of articles, and even displays the dates pretty well.
Conclusion
OK, deep breath, we're finally at the end.
This was quite fun to write, and I hope you enjoyed reading it, and learning about my news server. My real hope is that somebody finds a use for it, either on it's own or as part of the MERN-template.
Next time probably won't be so in-depth. Maybe.
My name is Kayne Ruse of KR Game Studios, you can find me on the net as Ratstail91, usually raving about APIs and scrawling RESTful queries on the walls.
Egg Trainer Website: https://eggtrainer.com/
MERN-template repo: https://github.com/krgamestudios/MERN-template
news-server repo: https://github.com/krgamestudios/news-server
Business Inquiries: [email protected]