Simple Microblogging: Dark API
A simple intro to Dark and Svelte, building a simplified Twitter-like microblog.
Lexicon
Dark
Also known as Darklang (the SEO term), it’s a deployless backend with its own functional programming language and editor.
Svelte
A Server-Side Generated (SSG) JavaScript framework that is fast and elegant.
🐇 Into the Rabbit Hole
Here’s what we are doing:
Design a simplified version of Twitter, where users can post tweets, follow/unfollow others, and is able to see the 10 most recent tweets in the user’s news feed.
Some useful functions to include would be getNewsFeed(userid)
, postTweet(userid, tweet)
, follow(follower, followee)
, and unfollow(follower, followee)
.
First Read(s) Analysis
The first feeling is that even for a simplified version of Twitter, there’s some sort of login implied. We need to be able to tell who the user is to show the users a feed, who they’re following and offer a follow/unfollow experience. So we need:
- a user datastore with a “following” field to keep a list of user IDs
- a tweet datastore with the user ID that posted
- persistence, to know who is “logged in” and making the actions
- a simplified login/logout
- follow, unfollow and tweet endpoints
Basic Design
He’s our empty canvas:
Backend
Let’s create the two datastores
Now, we’ll ping a nonexisting endpoint with the data to sign up
$ curl -X POST -H 'Content-Type: application/json' -d '{"name": "Thomas", "email": "tom@tomalcala.com"}' https://tomalcala-microblog.builtwithdark.com/signup
404 Not Found: No route matches
Of course no route matches. But now it appears in our 404s in the Dark canvas, so we can create it and see the request that was made to it.
- Now, saving the user and returning it. The
DB::set
function upserts in a datastore, so better try to fetch the user before, to not override the following. Tip: There is a trick, because by default, Dark handles errors with the Error Rail, so it bypasses the match Nothing and breaks because the code is incomplete. To circumvent that, on theDB::get
, pressctrl + \
to open the control panel, and choosetake-function-off-rail
.
Ready!
$ curl -X POST -H 'Content-Type: application/json' -d '{"name": "Thomas", "email": "tom@tomalcala.com"}' https://tomalcala-microblog.builtwithdark.com/signup
{ "email": "tom@tomalcala.com", "following": [], "name": "Thomas" }
- Tweet!
For fun, it’s going to be called posting, and tweets => microposts/posts :laughing:
Again, let’s start with the kind of request we want to send: a POST request to
/post
with a JSON object containing the post’s text. We also add a dummy authorization header containing the user’s email, we’ll use that to determine who posts. This should be done better with a proper auth, but it’s fine for now for this exercise.
$ curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer tom@tomalcala.com' -d '{"text": "My first post!"}' https://tomalcala-microblog.builtwithdark.com/post
404 Not Found: No route matches
- Edit Datastores Now, we have made a mistake (on purpose), it’d be a lot better to keep followers/followees in its own datastore, so we don’t hit the critical users datastore all the time to update them. As you can see, both datastores are locked because they hold data. So the migration will have to be done with some REPLs and a temporary user datastore.
First, create a TempUsers
datastore with the data we want to keep, and a REPL to map existing users, insert them in, and delete them from the old datastore.
Now that the Users datastore is empty, we can edit it and remove the “following” field. Then restore the users with another REPL, delete them from the temporary datastore, and that’s it
… And now we can delete all the temporary stuff. Starting with the REPLs, as Dark won’t let you delete datastores, functions etc while there’s still a canvas item pointing at them.
User Function We are going to need to retrieve a user from the authorization header more frequently, so let’s put this logic into a separate function. Functions happen to show in an isolated canvas, but datastores are available to them. And now, to modify the
/post
endpoint to use the new function:Follow and Unfollow We’ll create the follow and unfollow endpoints, that will write and delete in the Followers datastore. As always, let’s start with the requests to create the 404.
curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer tom@tomalcala.com' -d '{"followee": "hysteron@tomalcala.com"}' https://tomalcala-microblog.builtwithdark.com/subscribe
404 Not Found: No route matches
curl -X DELETE -H 'Content-Type: application/json' -H 'Authorization: Bearer tom@tomalcala.com' -d '{"followee": "hysteron@tomalcala.com"}' https://tomalcala-microblog.builtwithdark.com/subscribe
404 Not Found: No route matches
Follow (subscribe) looks like this: Right now, complex queries aren’t available in Dark, so I fetch all the logged in user’s followings, and check if the follower/followee relation exists in the list.
Now unsubscribe, which uses an endpoint called /subscribe
too, but with a DELETE
HTTP method:
- News Feed
Let’s make an endpoint to retrieve the list of posts and display the last 10. For logged in users, the list has to be filtered to only show their followee’s posts, so we’ll first check the Authorization header to determine if the user is logged in. Trace development oblige, we start with a query to a non-existing endpoint to get the trace:
curl -X GET -H 'Content-Type: application/json' -H 'Authorization: Bearer tom@tomalcala.com' https://tomalcala-microblog.builtwithdark.com/feed
404 Not Found: No route matches
… And this is how ot looks like: we get the user if there’s an authorization header, get the posts, reverse them to get the newest first, then filter by followee if the user is logged in.
Done!
We have everything required on the backend now. Here are some ideas for improvements:
- real authorization token management
- limit queries to not fetch all posts
- error and wrong input handling
- user handle instead of emails for data privacy
Next
We’ll make a Svelte frontend for this!!
Shoutout
- A big “thank you” to MC Cassidoo Cassidy Williams for her newsletter, this was made based on her weekly interview question. I strongly advise that you subscribe to her newsletter if you haven’t already!