cover image

How To Create a dev.to API Wrapper in PHP to Fetch your Latest Posts

The dev.to platform is becoming more and more popular among content producers, specially those focused on sharing dev-focused tutorials and articles. Meanwhile, the ongoing discussion about owning your content has a valid point, even though that creates new demands and questions around how to give it more visibility and reach a wider audience, something that a platform such as dev.to facilitates immensely.

We can have both!

Luckily for us, dev.to has a public API that you can use to fetch all your posts, and you are free to do as you wish with your own content.

In this tutorial, you'll learn how to create a wrapper around the dev.to API, using "vanilla" PHP (without userland dependencies). We'll build a class to fetch your latest dev.to posts using the Curl PHP extension.

Prerequisites

You'll need PHP (php-cli is enough), and the php-curl extension installed on your local machine or remote development server.

We'll run this script from the command line, but you can also run it from a browser if you prefer. In this case, you'll need to run the following command to use PHP's built-in web server to test the script:

php -S 0.0.0.0:8000

This will run a test server using the current directory as document root. You will be able to access it by pointing your browser to localhost:8000.

1. Dev.to API Overview

The dev.to API has a few endpoints that require authorization via a personal api token, but you won't need that for fetching public posts from a user.

To fetch your posts, you'll connect to the /articles endpoint and provide a username parameter with your own dev.to user.

You can try this query from your browser:

https://dev.to/api/articles?username=erikaheidi

In the next step, we'll use curl to query for this endpoint and manipulate the results in a PHP script.

2. Using Curl to Query the API

The php-curl extension allows us to make HTTP queries in PHP and is a popular choice for connecting and fetching data from APIs.

The following code will use curl functions to connect to the dev.to API and pull the latest posts from the specified user:

<?php

$user = "erikaheidi";
$endpoint = "https://dev.to/api/articles?username=" . $user;

$curl = curl_init();
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_URL => $endpoint,
]);

$response = curl_exec($curl);
curl_close($curl);

$articles = json_decode($response, true);
print_r($response);

This code starts by defining the username whose posts we'll be fetching, and the endpoint to query for.

We then initialize a new Curl resource, and use the curl_setopt_array function to define the details of our query. The CURLOPT_RETURNTRANSFER guarantees that the output produced by the query will be fully returned in the results, and the CURLOPT_URL option defines the query endpoint.

The curl_exec function makes the query and return its results to a $response variable. We then close the Curl resource, and use json_decode to decode the returned json into an array that we can later loop through to exhibit the posts.

The print_r method is a debug method to show us detailed information about the $articles array.

When you run this code (either from your command line or from a browser), you should get output similar to this:

Array
(
    [0] => Array
        (
            [type_of] => article
            [id] => 261347
            [title] => Which Techie Are You?
            [description] => Our desks tell a lot about ourselves, don't they? What is your style? 
            [cover_image] => https://res.cloudinary.com/practicaldev/image/fetch/s--PkX-bPVo--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/76j8y68yfscszyf3ripv.png
            [readable_publish_date] => Feb 14
            [social_image] => https://res.cloudinary.com/practicaldev/image/fetch/s--m81Jlno9--/c_imagga_scale,f_auto,fl_progressive,h_500,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/76j8y68yfscszyf3ripv.png
            [slug] => which-techie-are-you-1251
            [path] => /erikaheidi/which-techie-are-you-1251
            [url] => https://dev.to/erikaheidi/which-techie-are-you-1251
            [canonical_url] => https://dev.to/erikaheidi/which-techie-are-you-1251
            [comments_count] => 109
            [positive_reactions_count] => 121
            [collection_id] => 
            [created_at] => 2020-02-14T07:18:34Z
            [edited_at] => 2020-02-21T16:01:57Z
            [crossposted_at] => 
            [published_at] => 2020-02-14T11:31:10Z
            [last_comment_at] => 2020-02-25T09:17:36Z
            [published_timestamp] => 2020-02-14T11:31:10Z
            [tag_list] => Array
                (
                    [0] => discuss
                    [1] => illustrations
                    [2] => comics
                    [3] => humour
                )

            [tags] => discuss, illustrations, comics, humour
            [user] => Array
                (
                    [name] => Erika Heidi
                    [username] => erikaheidi
                    [twitter_username] => erikaheidi
                    [github_username] => erikaheidi
                    [website_url] => http://heidislab.com
                    [profile_image] => https://res.cloudinary.com/practicaldev/image/fetch/s--ABUlwZrQ--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/162988/5faf315a-4b14-4103-8640-983dfe9d57c2.jpg
                    [profile_image_90] => https://res.cloudinary.com/practicaldev/image/fetch/s--V9Gwrj-u--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/162988/5faf315a-4b14-4103-8640-983dfe9d57c2.jpg
                )

            [flare_tag] => Array
                (
                    [name] => discuss
                    [bg_color_hex] => #000000
                    [text_color_hex] => #FFFFFF
                )

        )

    [1] => Array
        (
            [type_of] => article
            [id] => 252194
            [title] => A Git from the Future (Comic)
            [description] => When working with Git, we often clone existing projects, since this is part of a typical collaboration workflow. What if we want to bootstrap a whole new project of our own?
            [cover_image] => https://res.cloudinary.com/practicaldev/image/fetch/s--WsP0wEBA--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/pvb1vbr5k5tirzqxhlp2.jpg
            [readable_publish_date] => Jan 31
            [social_image] => https://res.cloudinary.com/practicaldev/image/fetch/s--2m-4zDiP--/c_imagga_scale,f_auto,fl_progressive,h_500,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/pvb1vbr5k5tirzqxhlp2.jpg
            [slug] => a-git-from-the-future-comic-p86
            [path] => /erikaheidi/a-git-from-the-future-comic-p86
            [url] => https://dev.to/erikaheidi/a-git-from-the-future-comic-p86
            [canonical_url] => https://dev.to/erikaheidi/a-git-from-the-future-comic-p86
            [comments_count] => 15
            [positive_reactions_count] => 147
            [collection_id] => 4483
            [created_at] => 2020-01-31T11:12:49Z
            [edited_at] => 2020-01-31T11:51:03Z
            [crossposted_at] => 
            [published_at] => 2020-01-31T11:48:56Z
            [last_comment_at] => 2020-02-10T04:59:03Z
            [published_timestamp] => 2020-01-31T11:48:56Z
            [tag_list] => Array
                (
                    [0] => git
                    [1] => beginners
                    [2] => comics
                    [3] => illustrated
                )

            [tags] => git, beginners, comics, illustrated
            [user] => Array
                (
                    [name] => Erika Heidi
                    [username] => erikaheidi
                    [twitter_username] => erikaheidi
                    [github_username] => erikaheidi
                    [website_url] => http://heidislab.com
                    [profile_image] => https://res.cloudinary.com/practicaldev/image/fetch/s--ABUlwZrQ--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/162988/5faf315a-4b14-4103-8640-983dfe9d57c2.jpg
                    [profile_image_90] => https://res.cloudinary.com/practicaldev/image/fetch/s--V9Gwrj-u--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/162988/5faf315a-4b14-4103-8640-983dfe9d57c2.jpg
                )

        )

...
)

You'll notice that the results contain only a summary of the articles information, and doesn't contain the body of the article itself. Depending on your use case, you might want to fetch the content of the article as well; this will require an additional query for each article in the list.

In the next section, we'll refactor this code to turn it into a class, and then we'll include a new method to fetch individual articles too.

3. Creating an API wrapper class

So far, we've seen how to make queries to the dev.to API using the Curl PHP extension to fetch a list containing your most recent posts. We'll now create a Wrapper class to facilitate handling these operations and reusing the code in multiple locations.

Create a new folder in your directory of choice and a new file named DevtoWrapper.php inside of it.

The following code defines a new class named DevtoWrapper containing the code we've seen in the last section, now refactored into a class:

<?php

class DevtoWrapper
{
    static $ARTICLES_ENDPOINT = 'https://dev.to/api/articles';

    public function fetchArticlesFromUser($username)
    {
        return $this->get(self::$ARTICLES_ENDPOINT . "?username=" . $username);
    }

    private function get($endpoint)
    {
        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_URL => $endpoint,
        ]);

        $response = curl_exec($curl);
        curl_close($curl);

        return json_decode($response, true);
    }
}

We've created a get method with the portion of the code that would get duplicated if we were to include new methods that query the API, as we're going to do next.

But first, let's replace the test.php code to include that class file, instantiate a new DevtoWrapper object and call the fetchArticlesFromUser method:

<?php

require('DevtoWrapper.php');

$user = "erikaheidi";
$wrapper = new DevtoWrapper();

$articles = $wrapper->fetchArticlesFromUser($user);
print_r($articles);

Running the test.php script now will produce the same output as before.

Now you can include additional methods to the DevtoWrapper class. We'll include a method to call the /articles/{article_id} endpoint, which will fetch the full article information including the body of content in both markdown and html.

This is how the updated DevtoWrapper class looks after including the fetchArticle method:

<?php

class DevtoWrapper
{
    static $ARTICLES_ENDPOINT = 'https://dev.to/api/articles';

    public function fetchArticlesFromUser($username)
    {
        return $this->get(self::$ARTICLES_ENDPOINT . "?username=" . $username);
    }

    public function fetchArticle($article_id)
    {
        return $this->get(self::$ARTICLES_ENDPOINT . "/" . $article_id);
    }

    private function get($endpoint)
    {
        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_URL => $endpoint,
        ]);

        $response = curl_exec($curl);
        curl_close($curl);

        return json_decode($response, true);
    }
}

After updating your DevtoWrapper class, you can now obtain the full content of all your articles by looping through the list and calling the fetchArticle method for each article.

The following code will fetch your 30 most recent articles and save their full markdown body into local .md files inside a directory called articles. Make sure you create this directory before running the code:

<?php

require('DevtoWrapper.php');

$user = "erikaheidi";
$articles_dir = "articles";

$wrapper = new DevtoWrapper();

$articles = $wrapper->fetchArticlesFromUser($user);

foreach ($articles as $article) {
    echo "Importing " . $article['title'] . "...\n";

    $filename = $article['slug'] . '.md';
    $full_article = $wrapper->fetchArticle($article['id']);

    try {
        $file = fopen($articles_dir . '/' . $filename, 'w+');
        fwrite($file, $full_article['body_markdown']);
        fclose($file);
    } catch (Exception $e) {
        echo "There was an error while trying to save the file.";
    }
}

When you run this code, it will output a message telling which article is being imported at each iteration of the foreach loop. When it is finished, check your articles folder - you should find your articles there, in individual .md files containing the full markdown body as published on your dev.to account.

ls articles
a-git-from-the-future-comic-p86.md
an-introduction-to-3d-printing-ln1.md
a-primer-on-basic-electronics-and-circuits-n3e.md
bootstrapping-a-cli-php-application-in-vanilla-php-4ee.md
building-minicli-autoloading-command-namespaces-3ljm.md
creativity-is-the-pipeline-2pm6.md
...

Importing your dev.to posts into markdown files can be very useful if you want to display the full content on your own blog or site, but you can also use the listing and link back to dev.to if you'd prefer to only exhibit a summary of your posts.

Conclusion

In this tutorial, we've seen how to import your dev.to articles into local markdown files that you can then aggregate to an existing blog or website.

As next steps, you could use a markdown parser such as ParseDownExtra to convert the markdown content into HTML, or use a database to persist your articles content along with all the metadata associated with them, making it easier for searching and listing these posts on your own blog or website.

tutorial, PHP, apis, meta