Souping Up the DEV.to API with Vala
Tags: vala, tutorial, showdev
Published: 2022-06-29 02:15:15

---
published: true
title: Souping Up the DEV.to API with Vala
cover_image: https://twirp.km-w.net/devto/dev-export.png
tags: vala, tutorial, showdev
---
I'm not saying [Vala](https://vala.dev) is the language of the future. All I'm saying is some great apps are being built in it.

[ThiefMD](https://thiefmd.com)[^fn-made-by-me] recently added support for publishing to DEV.to. ThiefMD accomplishes this with [Vala](https://vala.dev), and today I hope to show you how.

[^fn-made-by-me]: I make ThiefMD 😻️.

![ThiefMD Connection Manager](https://twirp.km-w.net/devto/dev-connection.png)

The [DEV API](https://developers.forem.com/api/) is well documented. This post is more about **Vala**vangelism (it's a great language). For API calls across the net, Vala has bindings for both [cURL](https://curl.se/) and [libsoup](https://wiki.gnome.org/Projects/libsoup)[^fn-why-soup]. libsoup integrates with [Vala's Main Loop](https://naaando.gitbooks.io/the-vala-tutorial/content/en/5-advanced-features/the-main-loop.html) and has a nice [API](https://valadoc.org/libsoup-2.4/Soup.html). libsoup + [json-glib](https://wiki.gnome.org/Projects/JsonGlib) provides a fun and friendly way to interact with REST API's.

[^fn-why-soup]: If you're not using GLib or GTK, cURL is a more portable solution.

## Prerequisites

I'm on Ubuntu 22.04. For dependencies I'll run:

```bash
sudo apt install build-essential valac libsoup2.4-dev libjson-glib-dev meson ninja-build
```

## Abstracting the WebCall

For performing REST API calls, I created a class to wrap creating a [Soup.Session](https://valadoc.org/libsoup-2.4/Soup.Session.html) and [Soup.Message](https://valadoc.org/libsoup-2.4/Soup.Message.html).

```vala
/**
 * WebCall wraps a Soup.Session and handles managing Soup Messages
 */
private class WebCall {
    private Soup.Session session;
    private Soup.Message message;
    private string url;
    private string body;
    private bool is_mime = false;

    public string response_str;
    public uint response_code;

    /**
      * Constructs a Soup.Session for the given endpoint
      * 
      * @param endpoint The URL to connect to
      * @param api The path the API rests at
      */
    public class WebCall (string endpoint, string api) {
        url = endpoint + api;
        session = new Soup.Session ();
        body = "";
    }

    /**
      * Sets the data body for this request
      * 
      * @param data The data to populate the body with
      */
    public void set_body (string data) {
        body = data;
    }

    /**
      * Constructs a GET Soup.Message for this session
      */
    public void set_get () {
        message = new Soup.Message ("GET", url);
    }

    /**
      * Constructs a DELETE Soup.Message for this session
      */
    public void set_delete () {
        message = new Soup.Message ("DELETE", url);
    }

    /**
      * Constructs a POST Soup.Message for this session
      */
    public void set_post () {
        message = new Soup.Message ("POST", url);
    }

    /**
      * Adds an HTTP header to the current Soup.Message
      * 
      * @param key The HTTP Header Key to add
      * @param value The value for the HTTP Header
      */
    public void add_header (string key, string value) {
        message.request_headers.append (key, value);
    }

    /**
      * Performs the Session call for the current Message.
      * Returns true if the call receives a success code.
      * Returns false if the call fails or receives empty data.
      */
    public bool perform_call () {
        MainLoop loop = new MainLoop ();
        bool success = false;
        debug ("Calling %s", url);

        // The DEV API requires a User Agent
        add_header ("User-Agent", "Vala-to-Dev/0.1");
        if (body != "") {
            message.set_request ("application/json", Soup.MemoryUse.COPY, body.data);
        }

        session.queue_message (message, (sess, mess) => {
            response_str = (string) mess.response_body.flatten ().data;
            response_code = mess.status_code;

            // Validate we have a string response and success HTTP code
            if (response_str != null && response_str != "" &&
                response_code >= 200 && response_code <= 250)
            {
                success = true;
            }
            loop.quit ();
        });

        loop.run ();
        return success;
    }
}
```

`new WebCall` will construct our Soup.Session. The `set_post`, `set_get` and etc construct the proper Soup.Message type. `perform_call` executes our REST API calls. `MainLoop loop = new MainLoop ();` with `session.queue_message` allows us to let the GUI to continue to run while web requests happen in the background.

With this class, we can easily talk to the DEV API. JSON-GLib will help us speak the DEV API language.

## Speaking JSON from Vala

For a easy example, we're going to be checking [The authenticated user](https://developers.forem.com/api/#operation/getUserMe). The sample output looks like:

```js
{
    "type_of": "user",
    "id": 1234,
    "username": "bob",
    "name": "bob",
    "summary": "Hello, world",
    "twitter_username": "bob",
    "github_username": "bob",
    "website_url": null,
    "location": "New York",
    "joined_at": "Jan 1, 2017",
    "profile_image": "https://res.cloudinary.com/...jpeg"
}
```

Using JSON-GLib, our class representing the object looks like this.

```vala
public class UserResponse : GLib.Object, Json.Serializable {
    public string type_of { get; set; }
    public int id { get; set; }
    public string username { get; set; }
    public string name { get; set; }
    public string summary { get; set; }
    public string twitter_username { get; set; }
    public string github_username { get; set; }
    public string website_url { get; set; }
    public string location { get; set; }
    public string joined_at { get; set; }
    public string profile_image { get; set; }
}
```

Using `Json.Serializable` makes it easy for us to go from a Vala Object to a JSON representation.

## Making the API Call

Once we have our [DEV API Key](https://developers.forem.com/api/#section/Authentication), we're ready to start sending messages. Our validation of the API Key can look something like:

```vala
/**
 * Retrieves the username corresponding to the API-Key.
 * Returns true if the API Key worked, false otherwise.
 * 
 * @param username Receives the alias associated with the API-Key
 * @param api_key The API KEY to use for authentication
 */
public bool get_authenticated_user (
    out string username,
    string api_key)
{
    // Create a WebCall to handle this session
    WebCall authentication = new WebCall ("https://dev.to", "/api/users/me");
    // The Authentication API expects a GET request
    authentication.set_get ();
    // Add our API Key
    authentication.add_header ("api-key", api_key);

    // Perform our call
    bool res = authentication.perform_call ();
    if (!res) {
        return false;
    }

    // Parse the reponse
    Json.Parser parser = new Json.Parser ();
    parser.load_from_data (authentication.response_str);
    Json.Node data = parser.get_root ();
    UserResponse response = Json.gobject_deserialize (
        typeof (UserResponse),
        data)
        as UserResponse;

    username = response.username;
    return true;
}
```

Boom! Now we can validate our API key.

We can do something similar for [createArticle](https://developers.forem.com/api/#operation/createArticle), and we did! The code is at [github.com/ThiefMD/forem-vala](https://github.com/ThiefMD/forem-vala/).

## Thanks

Dear reader, I think your great. With Vala, I think you'll build great things! So go out and build, and don't hesitate to ask for help.

![ThiefMD Export View](https://twirp.km-w.net/devto/dev-export.png)

[ThiefMD](https://thiefmd.com) uses this library to allow exporting documents to DEV. With [Spell Checking](http://gtkspell.sourceforge.net/), [Grammar Checking](https://dev.to/twirp/adding-grammar-checking-to-gtk4s-textview-3lo6), and [Write-Good](https://writegood.thiefmd.com/) ThiefMD will have you missing your teacher's red pen in no time. I mean, writing and publishing great DEV articles in no time.