discord-php/http
Async PHP HTTP client for the Discord REST API (PHP 7.4+). Works with an event loop (e.g., React) and PSR-3 logging. Provides get/post/put/patch/delete plus queueRequest, returns decoded JSON promises, and includes Endpoint constants with bind() for rate-limit buckets.
composer require discord-php/http
composer require monolog/monolog
app/Providers/DiscordHttpServiceProvider.php):
namespace App\Providers;
use Discord\Http\Http;
use Discord\Http\Drivers\React;
use Illuminate\Support\ServiceProvider;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use React\EventLoop\Factory;
class DiscordHttpServiceProvider extends ServiceProvider
{
public function register()
{
$loop = Factory::create();
$logger = (new Logger('discord-http'))->pushHandler(new StreamHandler(storage_path('logs/discord.log')));
$http = new Http(config('services.discord.token'), $loop, $logger);
$http->setDriver(new React($loop));
$this->app->singleton('discord.http', function () use ($http, $loop) {
return [$http, $loop];
});
}
}
config/app.php:
'providers' => [
// ...
App\Providers\DiscordHttpServiceProvider::class,
],
config/services.php:
'discord' => [
'token' => env('DISCORD_BOT_TOKEN'),
],
use Discord\Endpoint;
$discordHttp = app('discord.http')[0];
$discordHttp->get(Endpoint::CURRENT_USER_APPLICATION)
->done(function ($response) {
logger()->info('Bot info:', ['data' => $response]);
})
->fail(function ($e) {
logger()->error('Failed to fetch bot info:', ['error' => $e->getMessage()]);
});
In your Laravel application, ensure the event loop runs in a separate process (e.g., Laravel Octane, a worker, or a CLI command):
php artisan discord:http-worker
Or in a Laravel command:
// app/Console/Commands/RunDiscordHttp.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use React\EventLoop\LoopInterface;
class RunDiscordHttp extends Command
{
protected $signature = 'discord:http-worker';
protected $description = 'Run Discord HTTP event loop';
public function handle()
{
$loop = app('discord.http')[1];
$loop->run();
}
}
Wrap the HTTP client in a Laravel service to abstract Discord logic:
// app/Services/DiscordService.php
namespace App\Services;
use Discord\Http\Http;
use Discord\Endpoint;
class DiscordService
{
protected $http;
public function __construct(Http $http)
{
$this->http = $http;
}
public function fetchGuildMessages(string $guildId, string $channelId, int $limit = 10)
{
$endpoint = Endpoint::bind(Endpoint::CHANNEL_MESSAGES, $channelId);
return $this->http->get($endpoint, ['limit' => $limit]);
}
public function createMessage(string $channelId, string $content)
{
$endpoint = Endpoint::bind(Endpoint::CHANNEL_MESSAGE_CREATE, $channelId);
return $this->http->post($endpoint, json_encode(['content' => $content]));
}
}
Register the service in AppServiceProvider:
$this->app->bind(DiscordService::class, function ($app) {
return new DiscordService($app['discord.http'][0]);
});
Use Laravel queues to offload Discord API calls:
// app/Jobs/FetchDiscordGuildData.php
namespace App\Jobs;
use App\Services\DiscordService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class FetchDiscordGuildData implements ShouldQueue
{
use Queueable;
protected $guildId;
public function __construct(string $guildId)
{
$this->guildId = $guildId;
}
public function handle(DiscordService $discord)
{
$endpoint = Endpoint::bind(Endpoint::GUILD, $this->guildId);
$response = $discord->http->get($endpoint);
$response->done(function ($guild) {
// Process guild data
})->fail(function ($e) {
logger()->error("Failed to fetch guild: {$e->getMessage()}");
});
}
}
Always use Endpoint::bind() for endpoints with parameters to ensure proper rate-limit bucketing:
// Correct: Uses rate-limit bucketing
$endpoint = Endpoint::bind(Endpoint::CHANNEL_MESSAGE, '1234567890', '9876543210');
$discordHttp->get($endpoint)->done(...);
// Incorrect: May bypass rate limits
$discordHttp->get('channels/1234567890/messages/9876543210')->done(...);
Use then() for chaining async operations:
$discordHttp->get(Endpoint::CURRENT_USER_APPLICATION)
->then(function ($bot) {
return $discordHttp->get(Endpoint::bind(Endpoint::GUILD_MEMBERS, $bot->guilds[0]->id, 'user_id_here'));
})
->done(function ($member) {
logger()->info('Member data:', ['member' => $member]);
})
->fail(function ($e) {
logger()->error('Async chain failed:', ['error' => $e->getMessage()]);
});
Implement exponential backoff for retries:
use Discord\Http\Exceptions\DiscordHttpException;
$discordHttp->get(Endpoint::GUILD, ['guild_id' => '1234567890'])
->fail(function (DiscordHttpException $e) {
if ($e->getCode() === 429) { // Rate limited
$retryAfter = $e->getRetryAfter();
sleep($retryAfter);
$this->retry();
}
});
For high-performance setups, use Laravel Octane with Swoole:
// routes/octane.php
use App\Services\DiscordService;
use Discord\Http\Drivers\Swoole;
$octane->worker('discord', function () {
$loop = \Swoole\Event::getEventLoop();
$http = new Http(config('services.discord.token'), $loop, app('log'));
$http->setDriver(new Swoole($loop));
app()->singleton('discord.http', function () use ($http, $loop) {
return [$http, $loop];
});
});
// ❌ Bad: Running in a web route
$loop->run(); // Will block the entire request
// ✅ Good: Run in a worker or Octane
React\EventLoop\LoopInterface for dependency injection:
$loop = app('discord.http')[1]; // Inject the loop
// ❌ Avoid: May bypass rate limits
$discordHttp->get('channels/1234567890/messages/9876543210');
// ✅ Correct: Uses proper bucketing
$endpoint = Endpoint::bind(Endpoint::CHANNEL_MESSAGE, '1234567890', '9876543210');
$discordHttp->get($endpoint);
$logger = (new Logger('discord-http'))
->pushHandler(new StreamHandler(storage_path('logs/discord.log'), Logger::DEBUG));
429 Too Many Requests errors. These indicate rateHow can I help you explore Laravel packages today?