Simple Microblogging: Dark API

A simple intro to Dark and Svelte, building a simplified Twitter-like microblog.

Dark canvas

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:

Dark canvas

Backend

  1. Let's create the two datastores Dark canvas datastores

  2. 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. Dark canvas 404

  1. 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. Canvas sign up 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 the DB::get, press ctrl + \ to open the control panel, and choose take-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" }
  1. 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

Post endpoint

  1. 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. User backup

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 User restore

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

  1. 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. Dark function And now, to modify the /post endpoint to use the new function: Dark function

  2. 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: Dark function 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: Dark function

  1. 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. Dark feed

Done!

Dark canvas

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!