HTTP Requests and the Resource Builder
In this section, you’ll learn how to make web requests and easily parse the response to save site data or construct new resources like blog posts or collection entries.
Below is an example of making an HTTP GET request to a remote API, looping through an array parsed from the JSON response, and saving new posts based on each item.
The examples on this page use the HTTPX Ruby gem, but you can use any HTTP client. If you use a gem, just remember to run bundle add httpx (or your preferred gem) so that you can require it.
require "httpx"
class LoadPostsFromAPI < SiteBuilder
def build
HTTPX.get("https://domain.com/posts.json").json.each do |post|
add_resource :posts, "#{post["slug"]}.md" do
___ post
layout :post
categories post["taxonomy"]["category"].map { |category| category["slug"] }
date Bridgetown::Utils.parse_date(post["date"])
content post["body"]
end
end
end
end
Table of Contents #
The Resource Builder #
Adding content from an API to the site.data object is certainly useful, but an even more powerful feature is the Resource Builder. Call the add_resource method to generate resources which function in exactly the same way as if those files were already stored in your repository. It uses a special DSL, similar to Ruby Front Matter.
Here’s an example of creating a new blog post:
def build
add_resource :posts, "2020-05-17-way-to-go-bridgetown.md" do
layout :post
title "Way to Go, Bridgetown!"
author "rlstevenson"
content "It's pretty _nifty_ that you can add **new blog posts** this way."
end
end
This is the programmatic equivalent of saving a new file src/_posts/2020-05-17-way-to-go-bridgetown.md with the following contents:
---
title: Way to Go, Bridgetown!
author: rlstevenson
---
It's pretty _nifty_ that you can add **new blog posts** this way.
Collections #
You can save a resource in any collection:
add_resource :authors, "rlstevenson.md" do
name "Robert Louis Stevenson"
born 1850
nationality "Scottish"
end
You don’t even need to use a collection that’s previously been configured in initializers.rb or bridgetown.config.yml. You can make up new collections and use existing layouts to place your content within the appropriate templates, assuming the expected front matter is compatible.
add_resource :blogish, "fake-blog-post.html" do
layout :post
title "I'm a blog post…sort of"
date "2020-05-17"
content "<p>I might look like a blog post, but I'm <em>not!</em></p>"
end
That resource would then get written out to the /blogish/fake-blog-post/ URL.
Another aspect of the Resource Builder to keep in mind is that content is a “special” variable. Everything except content is considered front matter, and content is everything you’d add to a file after the front matter.
Customizing Permalinks #
If you’d like to customize the permalink of a new resource, you can specifically set the permalink front matter variable:
add_resource :posts, "blog-post.md" do
title "Strange Paths"
date "2019-07-23"
permalink "/path/to/the/:slug/"
content "…"
end
The post would then be accessible via /path/to/the/blog-post/.
Merging Hashes Directly into Front Matter #
If you have a hash of variables you’d like to merge into a resource’s front matter, you can use the ___ method.
vars = {
title: "I'm a Draft",
categories: ["category1", "category2"],
published: false
}
add_resource :posts, "post.html" do
___ vars
end
This is great when you have data coming in from external APIs and you’d like to inject all of that data into the front matter with a single method call.
Bear in mind that this doesn’t include your content variable. So you’ll still need to set that separately when using the ___ method, for example:
data = HTTPX.get(article_url).json
add_resource :pages, "articles/#{data["slug"]}.html" do
___ data
content data["body"]
end
DSL Scope #
If you’re not familiar with Ruby DSLs, you may run into an issue where you need to call a method from your builder plugin within add_resource and it’s not in scope. For example, this won’t work:
def string_value
"I'm a string!"
end
def build
add_resource :pages, "page.html" do
title string_value
content "Page content."
end
end
The reason it won’t work is because in this example, title is actually interpreted as a method call within the DSL block, which means string_value is a similar call. That would be fine if you’d already added string_value as a front matter key, in which case string_value would return that front matter variable. But in this case, you want to use the string_value method of your plugin.
To accomplish that, provide a lambda using the from: -> { } syntax. Let’s rewrite the above example to work as expected:
def string_value
"I'm a string!"
end
def build
add_resource :pages, "page.html" do
title from: -> { string_value }
content "Page content."
end
end
Now the title front matter variable will be set to “I’m a string”.
Builder Lifecycle and Data Files #
Something to bear in mind is that that code in your build method is run as part of the site’s pre_read hook, which means that no data or content in your site repository has yet been loaded at that point. So you can’t, say, build resources based on existing data files as you might assume:
def build
# THIS WON'T WORK!!!
site.data[:stuff_from_the_repo].each do |k, v|
add_resource :stuff, "#{k}.md" do
___ v
content v[:content]
end
end
end
Instead, what you can do is define a post_read custom hook and then read in the data:
def build
hook :site, :post_read do
site.data[:stuff_from_the_repo].each do |k, v|
add_resource :stuff, "#{k}.md" do
___ v
content v[:content]
end
end
end
end
Conclusion #
As you’ve seen from these examples, you can use data from external APIs to create new content for your Bridgetown website with the add_resource method provided by the Builder API. While there are numerous benefits to storing content directly in your site repository, Bridgetown gives you the best of both worlds—leaving you to decide where you want your content to live and how you’ll put it to good use as you build your site.