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.
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.
Top comments (7)
What I like about what you‘re doing, here and on GitHub is that you‘re reminding us at what PHP is good at: getting to results fast and easy. Many of us (myself included) are so trapped in framework and 3rd party library thinking that it‘s easy to forget how simple it can be. Thank you for reminding me, I enjoy your content a lot! 🌺
Thank you so much for this comment! ❤️❤️❤️
I really liked this article, even though I don't use PHP.
It's nice to be able to fiddle around with it without requiring a whole web server, thanks to the command line
php -f script.php
. 🎩Hi, here is an async curl version of your example. Maybe someone wants to play with it. :)
⇾ github.com/voku/httpful/blob/maste...
Great post and great cover drawing, too :)
Thank you ☺️❤️
Thanks for sharing Good Article.