Perl
async programming
REST API
HTTP request
tutorial

How to easily do an async REST request in Perl?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Asynchronous REST calls in Perl are typically done with event-loop libraries rather than manual thread management. A common modern choice is Mojo::UserAgent from Mojolicious, which supports non-blocking HTTP requests and composable callbacks or promises. This makes it easier to issue concurrent API calls and aggregate results efficiently.

The key design points are event-loop lifecycle, error handling, and backpressure when making many requests.

Core Sections

1. Simple async GET with callback

perl
1use strict;
2use warnings;
3use Mojo::UserAgent;
4
5my $ua = Mojo::UserAgent->new;
6$ua->get('https://api.example.com/status' => sub {
7    my ($ua, $tx) = @_;
8    my $res = $tx->result;
9
10    if ($res->is_success) {
11        print $res->body, "\n";
12    } else {
13        warn "HTTP error: " . $res->code . "\n";
14    }
15
16    Mojo::IOLoop->stop;
17});
18
19Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

2. Issue multiple concurrent requests

perl
1my @urls = (
2  'https://api.example.com/a',
3  'https://api.example.com/b',
4  'https://api.example.com/c',
5);
6
7my $remaining = scalar @urls;
8for my $url (@urls) {
9    $ua->get($url => sub {
10        my ($ua, $tx) = @_;
11        my $res = $tx->result;
12        print "$url => " . ($res->is_success ? 'ok' : 'fail') . "\n";
13
14        $remaining--;
15        Mojo::IOLoop->stop if $remaining == 0;
16    });
17}
18Mojo::IOLoop->start;

3. Async POST with JSON payload

perl
1$ua->post('https://api.example.com/items' => json => {name => 'widget'} => sub {
2    my ($ua, $tx) = @_;
3    my $res = $tx->result;
4    print $res->body;
5});

4. Timeout and retry strategy

Configure request timeout and add retry wrapper logic to handle transient failures without blocking the loop.

5. Promise-style composition

Mojo::Promise allows cleaner async chains for sequential dependent API calls.

Common Pitfalls

  • Starting/stopping event loop incorrectly and hanging the script.
  • Launching unbounded concurrent requests and overwhelming remote APIs.
  • Ignoring non-success HTTP responses in callbacks.
  • Mixing blocking HTTP clients inside async flow.
  • Forgetting timeout/retry behavior for unstable networks.

Summary

Async REST in Perl is straightforward with event-loop tools like Mojo::UserAgent. Use non-blocking requests, manage loop lifecycle deliberately, and handle errors/timeouts explicitly. For multiple endpoints, coordinate concurrent calls with counters or promises. This approach gives efficient, maintainable async HTTP workflows without thread complexity.

A practical way to make this guidance durable is to convert it into a small runbook that includes prerequisites, expected environment versions, and a short verification sequence. Even strong teams lose time when troubleshooting steps live only in memory or chat history. A runbook should explicitly answer three questions: what to check first, what output confirms healthy behavior, and what output indicates a known failure mode. This level of clarity helps both experienced maintainers and newer contributors, and it reduces repeated investigation during incidents.

It is also valuable to create a tiny reproducible fixture for this topic. The fixture can be a minimal script, test case, sample request, or small dataset that demonstrates the correct behavior in isolation. When regressions appear after dependency upgrades, infrastructure changes, or framework migrations, that fixture becomes the fastest way to isolate whether the issue is environmental or logic-related. Keeping a focused fixture in source control gives you a stable benchmark across branches and release cycles.

For long-term reliability, pair documentation with one automated guardrail in CI. The guardrail should be narrow and fast: an import check, schema validation, endpoint contract test, deterministic unit test, or lightweight performance threshold. Avoid broad flaky checks that hide real signals. The goal is early, actionable feedback before code reaches production. If the same category of issue appears repeatedly, promote the manual troubleshooting step into automation so the system catches it first. Over time, this shifts effort from reactive debugging to preventive quality control and keeps the knowledge article relevant in real engineering workflows.

As a final hardening step, periodically rerun the sample code in a clean environment image and record results in version control. This catches ecosystem drift early and keeps implementation guidance aligned with real runtime behavior.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.