Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Postcodenl Api Bundle Laravel Package

keesschepers/postcodenl-api-bundle

A Symfony bundle that integrates the Postcode.nl API for looking up Dutch addresses and postcodes. It provides service configuration and a simple client wrapper so you can validate postcodes and retrieve street, city and coordinates within your Symfony application.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you install. This is a Symfony bundle, not a Laravel package. There is no extra.laravel manifest, no auto-discovery, and no service provider. If you are in a Laravel codebase, the correct "install" is not composer require keesschepers/postcodenl-api-bundle — it is treating the bundle as a ~50-line specification and writing your own adapter against the same REST endpoint. Copying request/response handling into a first-party class is cheaper than bridging Symfony DI and is under your maintenance.

If you are actually on Symfony (2/3-era):

  1. composer require keesschepers/postcodenl-api-bundle

  2. Register the bundle manually — it predates Flex, so add it to app/AppKernel.php (or config/bundles.php if it happens to be Flex-compatible).

  3. Configure credentials. postcode.nl uses HTTP Basic auth where the API key is the username and the secret is the password. Keep them in parameters.yml/.env, never inline in bundle config.

  4. Locate the real service before writing any code. Old bundles do not autowire:

    bin/console debug:container | grep -i postcode
    

    That gives you the actual service ID and the class to type-hint. Do not trust README examples from a 1-star repo — verify against the container.

First use case (95% of integrations): Dutch checkout / address-form prefill. User enters 1234AB + house number 42; you call the lookup and populate street and city into read-only fields.

Where to look first, in order:

  1. The bundle's Resources/config/services.xml — what is actually registered, what is the service ID.
  2. The shipped client class — the method signature, return type, and exception hierarchy you are stuck with.
  3. The postcode.nl API docs themselves — the bundle is a thin transport layer, so the API contract is the real documentation.

Implementation Patterns

Wrap it; never call the client from a controller. The bundle client is a leaky abstraction tied to a specific Guzzle major and exception hierarchy. Define your own interface and inject the bundle service into an implementation:

interface AddressLookup {
    /** @throws AddressNotFound */
    public function lookup(string $postcode, string $houseNumber, ?string $addition = null): Address;
}

When the bundle dies — which it will — you swap one class, not every call site.

Normalize input before it reaches the client. postcode.nl expects exactly six alphanumeric characters in 1234 AB form. Users type 1234 ab, 1234-ab, 1234AB , lowercase, or with full-width characters. Normalize once, in your wrapper:

$postcode = strtoupper(preg_replace('/[^0-9A-Za-z]/', '', $postcode));
if (!preg_match('/^[1-9][0-9]{3}[A-Z]{2}$/', $postcode)) {
    throw new InvalidPostcode();
}

Cheap local validation saves billable API calls and gives you a fast "did you mean" UX.

Laravel wiring. Bind the interface in a service provider and configure through config/services.php + .env:

// config/services.php
'postcode_nl' => [
    'key'     => env('POSTCODE_NL_KEY'),
    'secret'  => env('POSTCODE_NL_SECRET'),
    'base_uri'=> 'https://api.postcode.nl/rest/addresses/',
    'timeout' => 2,
],

// AppServiceProvider::register()
$this->app->bind(AddressLookup::class, PostcodenlAddressLookup::class);

Cache aggressively. Every lookup is a synchronous third-party HTTP call on a user-facing path, and the vendor bills per call. Key on normalized postcode + houseNumber + addition and set a TTL rather than caching forever — addresses change through demolition and renumbering:

return Cache::remember("pc:{$postcode}:{$number}:{$addition}", now()->addDays(30), fn () =>
    $this->client->lookup($postcode, $number, $addition)
);

Distinguish "not found" from "error" in the mapping. Callers conflate these constantly and it produces terrible UX. AddressNotFound means the postcode is genuinely wrong or the property is new/unregistered — surface a validation hint. A transport failure or 5xx means you failed — keep the manual address fields editable and let the user proceed.

Test with fakes, not the network. In Laravel, Http::fake() on your adapter; in Symfony, swap the client binding for a stub in config/services_test.yml. Assert on the request shape (path, auth header, postcode format) and the mapping, because response field names are the part that silently drifts.

Sequence for a real rollout: confirm credentials/terms → spike the raw call (curl/Postman) to fix exact request/response shape → build the adapter behind the interface → add cache + timeout + fallback → ship behind a feature flag on one low-risk form → measure latency and failure rate → broaden.

Gotchas and Tips

  • Supply-chain risk is the headline. 1 star, 0 dependents, unmaintained, low health score. Expect security scanning to block it and Composer to break it on every PHP/Symfony bump. Do not adopt as a production dependency in a new codebase.
  • Auth scheme confusion is the #1 silent failure. Older and current postcode.nl credential models differ; API key = username, secret = password, over Basic auth. A wrong scheme returns a 401 that many wrappers surface as an empty result — appearing as "postcode not found" rather than an auth problem. Log the HTTP status, not just the mapped outcome.
  • Credential expiry looks like no results. Add a health check that distinguishes 401/403 from 404, and alert on the former. A silently expired key in an address autofill is invisible until conversion drops.
  • Edge-case postcodes need explicit UX. New builds, business addresses, PO boxes, and house-number additions (bis, A, 1a) behave inconsistently. Not every valid postcode resolves to a residential address; design for "found partial" as a state.
  • Timeouts are non-negotiable. Sub-2s, plus a circuit breaker. A slow vendor should degrade to manual entry, not block checkout. Keep the manual address inputs visible always, not only on error — users on mobile with autofill will still type.
  • Rate limits and quota scale linearly with form submissions. A bot hammering the checkout form burns your paid quota and can throttle legitimate users. Cache, rate-limit per session/IP, and monitor call volume as a metric.
  • GDPR/PII. Postcode + house number resolves to a specific address and is personal data in NL. Decide where the result is stored, for how long, and your lawful basis; routing through a third party likely needs a processor agreement. Do this before you cache anything.
  • Nothing in the box covers resilience. No caching, retries, timeouts, circuit breaking, or observability. All of it is yours to write — budget for that in the estimate, not just "one day to integrate."
  • Extension points are limited but sufficient. Because everything is DI, you can decorate the bundle service or rebind the client class in the container for tests/stubs. That is the practical seam; do not extend the bundle itself.
  • Don't queue interactive lookups. Queue only for non-blocking use cases (back-office enrichment, CRM import normalization). Never put the checkout autofill behind a worker.
  • Vendor swap is the real architectural decision. postcode.nl vs. Pro6PP vs. PostcodeAPI.nu vs. Loqate vs. Google Places — and whether you need BE/DE next. The AddressLookup interface is what lets you answer that question later without a rewrite; the bundle is not.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums