smmehdisharifi / laravel-msgpack
Optional MessagePack content negotiation and binary API responses for Laravel
Requires
- php: ^8.1
- illuminate/console: ^9.0|^10.0|^11.0|^12.0
- illuminate/http: ^9.0|^10.0|^11.0|^12.0
- illuminate/routing: ^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
- rybakit/msgpack: ^0.7 || ^0.10
- symfony/http-foundation: ^6.0|^7.0|^8.0
Requires (Dev)
- orchestra/testbench: ^7.0|^8.0|^9.0|^10.0
Suggests
- ext-zlib: Enables gzip metrics in the msgpack:benchmark command
Provides
None
Conflicts
None
Replaces
None
- dev-master
- v2.2.0
- v2.1.0
- v2.0.0
- 1.0.0
- dev-feature/http-client-integration
- dev-feature/php-8-5-support
- dev-feature/api-resource-support
- dev-content/json-vs-messagepack-article
- dev-docs/readme-product-positioning
- dev-feature/msgpack-benchmark
- dev-chore/repository-standards
- dev-feature/content-negotiation
This package is auto-updated.
Last update: 2026-09-11 19:33:11 UTC
README
MessagePack content negotiation for Laravel APIs.
Keep JSON as the default. Let capable clients opt into compact binary responses without changing controllers.
Laravel Msgpack gives Laravel APIs an opt-in binary representation while preserving JSON for existing clients. Add the middleware to a route or route group, and let each client choose its representation with the Accept header.
Why this package?
MessagePack is a compact, schema-free binary format that can reduce wire size for suitable payloads. It is useful for high-volume APIs, mobile clients, internal services, and bandwidth-constrained applications.
This package is designed for incremental adoption:
- Existing clients continue to receive JSON.
- MessagePack clients opt in with
Accept: application/msgpack. - Responses include
Vary: Acceptfor correct HTTP caching. - Requests support both
application/msgpackand the legacyapplication/x-msgpackmedia type. - Errors follow the same negotiation, including unmatched routes and route exceptions.
- The benchmark helps you measure your own payloads instead of relying on universal performance claims.
Features
Accept-based response negotiation with JSON fallback- Wildcard and quality-factor handling with
406 Not Acceptablewhen every supported format is rejected - Request body decoding for MessagePack content types with parameters
response()->msgpack()with status and custom header supportrequest()->msgpack()access to the original decoded payload- Safe
400responses for invalid MessagePack payloads - Configurable request payload limit with
413responses - Configurable nesting-depth and value-count limits for decoded payloads
- Safe refusal of streamed, encoded, and non-JSON responses that cannot be represented as MessagePack
- Built-in Artisan benchmark for raw and gzip-compressed JSON/MessagePack payloads
- Laravel service provider and middleware auto-discovery
Compatibility
- PHP 8.1 through 8.5 tested in CI
- Laravel 9.x through 12.x
rybakit/msgpack0.7 and 0.10- Optional
ext-zlibsupport for gzip benchmark metrics
Installation
composer require smmehdisharifi/laravel-msgpack
Quick Start
Add the middleware to an API route or route group:
use Illuminate\Support\Facades\Route; Route::middleware('msgpack')->get('/api/profile', function () { return [ 'name' => 'Laravel', 'format' => 'negotiated', ]; });
No controller changes are required. Clients continue to receive JSON by default:
curl -i http://localhost/api/profile
A MessagePack-aware client opts into a binary response:
curl -sS -D - \
-H 'Accept: application/msgpack' \
-o profile.msgpack \
http://localhost/api/profile
The headers are printed to the terminal and the binary response body is saved to profile.msgpack:
Content-Type: application/msgpack Vary: Accept
If both formats are accepted, the higher q value wins and media-type wildcards are evaluated by specificity. The package returns 406 Not Acceptable when the client explicitly rejects every supported format.
The configured content_type is accepted as an explicit MessagePack response format. Additional entries in accept_content_types, such as the legacy application/x-msgpack type, are preserved when selected. The response macro always uses the configured content_type.
Request Decoding
MessagePack request bodies are decoded automatically:
use Illuminate\Http\Request; Route::middleware('msgpack')->post('/api/profile', function (Request $request) { return [ 'received' => $request->msgpack(), 'name' => $request->input('name'), ]; });
For map payloads, decoded values are also merged into Laravel's normal request input. The original value remains available through request()->msgpack(), including scalar and list payloads.
Invalid payloads do not reach the route handler. The middleware returns 400, or 413 when the configured payload limit is exceeded. A body containing more than one MessagePack value is rejected; the package accepts one complete value per request.
Explicit Responses
Use the response macro when a route should always return MessagePack:
return response()->msgpack([ 'message' => 'Created', ], 201, [ 'X-Request-Id' => $requestId, ]);
The macro follows the familiar Laravel response shape:
response()->msgpack($value, $status = 200, $headers = []);
API Resources and Pagination
Laravel JSON resources work with both the middleware and the explicit response macro. Resource collections and paginated resources keep their normal data, links, and meta structure after MessagePack negotiation:
use App\Http\Resources\UserResource; use App\Models\User; use Illuminate\Support\Facades\Route; Route::middleware('msgpack')->get('/api/users', function () { return UserResource::collection( User::query()->paginate(25), ); });
Clients that send Accept: application/msgpack receive the same resource
payload as a binary response. Clients that omit the header continue to receive
the normal JSON representation:
return response()->msgpack(new UserResource($user));
Encode and Decode
The facade is available for direct serialization:
use Msgpack; $data = ['name' => 'Laravel', 'type' => 'framework']; $packed = Msgpack::encode($data); $unpacked = Msgpack::decode($packed);
Benchmark
Run the built-in benchmark from a Laravel application's console:
php artisan msgpack:benchmark
The command uses a deterministic API-shaped fixture and measures payload size,
size reduction, encode time, and decode time. It also reports gzip size and
compression timings when ext-zlib is available. JSON uses the default
json_encode options used by Laravel's JSON response factory. Each operation
is warmed up once before timing, and reported timings are per iteration.
Useful options:
php artisan msgpack:benchmark --iterations=5000 php artisan msgpack:benchmark --iterations=5000 --json
The --json option is useful for CI or for recording results over time. An
indicative table result looks like this; timings depend on the PHP version and host:
MessagePack benchmark
Fixture: deterministic_api_payload
Iterations: 1000
Payload size JSON MessagePack Reduction
591 B 435 B 26.40 %
Encode time 0.001 ms 0.008 ms
Decode time 0.003 ms 0.007 ms
Gzip compression (level 6)
Compressed size 377 B 371 B 1.59 %
Compress time 0.009 ms 0.010 ms
Decompress time 0.004 ms 0.003 ms
MessagePack is not automatically faster or smaller for every payload. Use the benchmark with data representative of your application before choosing it as a transport format.
Benchmark Article
Read the full comparison, including the measured trade-offs between raw and gzip-compressed payloads:
- JSON vs MessagePack in Laravel: A Practical API Benchmark
- Laravel middleware documentation
- MessagePack specification
json_encodein the PHP manualgzencodein the PHP manual
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=msgpack-config
Default configuration:
return [ 'content_type' => 'application/msgpack', 'request_content_types' => [ 'application/msgpack', 'application/x-msgpack', ], 'accept_content_types' => [ 'application/msgpack', 'application/x-msgpack', ], 'max_request_size' => 10 * 1024 * 1024, 'max_depth' => 64, 'max_nodes' => 100000, ];
Set max_request_size, max_depth, or max_nodes to 0 or null to disable that package-level guard. The request-size check reads at most one byte beyond the configured limit and also rejects a larger declared Content-Length before decoding. Depth and value-count limits are checked while scanning the MessagePack value before it is materialized. Web-server and Laravel limits may still apply.
Responses selected as MessagePack must be JSON representations or already-encoded MessagePack responses. HTML, streamed responses, and responses with Content-Encoding return 406 instead of sending a body in an unexpected format. Representation-specific headers such as ETag, Digest, and Content-MD5 are removed when the JSON body is re-encoded.
JavaScript Client
The package works with standard MessagePack clients such as @msgpack/msgpack:
import { decode, encode } from '@msgpack/msgpack'; const response = await fetch('/api/profile', { headers: { Accept: 'application/msgpack' }, }); const payload = decode(new Uint8Array(await response.arrayBuffer())); await fetch('/api/profile', { method: 'POST', headers: { Accept: 'application/msgpack', 'Content-Type': 'application/msgpack', }, body: encode({ name: 'Laravel' }), });
Middleware Registration
The package registers the msgpack middleware alias automatically. If your application needs manual registration, add:
protected $middlewareAliases = [ 'msgpack' => \SmMehdiSharifi\LaravelMsgpack\Middleware\MsgpackMiddleware::class, ];
Testing
composer install ./vendor/bin/phpunit tests
The test suite covers round-trip serialization, response macros, content negotiation, JSON fallback, request decoding, malformed payloads, request limits, status codes, and custom headers.
Support the Project
If this package helps your API, star the repository to help other Laravel developers discover it. Bug reports, feature ideas, documentation improvements, and real-world benchmark results are welcome.
Contributing
Issues and pull requests are welcome. Please read CONTRIBUTING.md before starting work.
New behavior should include integration tests and an update to the HTTP contract documented above. Use the repository's issue forms and pull request template so reports and contributions remain consistent.
Security vulnerabilities must be reported privately according to SECURITY.md.
License
This package is open-sourced software licensed under the MIT license.