Building a REST API Endpoint in Drupal for External Apps
Many Australian studios running Drupal as the editorial backbone for a client site eventually face the same moment: a mobile team in Melbourne or an external partner in Sydney needs programmatic access to the content. Whether the goal is feeding a native app, syncing with a CRM, or pushing nodes into a reporting dashboard, the bridge is usually a REST endpoint exposed by Drupal itself. Drupal ships with REST support in core, which means you do not need a heavy custom module to get a workable surface up. The interesting work happens in how you shape resources, secure them, and keep them stable as your schema evolves.
Across councils in Brisbane, university marketing teams in Adelaide, and tourism sites in Perth, the pattern repeats. Drupal holds structured content, and an adjacent system wants a clean JSON payload. Building that bridge well is less about exotic code and more about decisions made early: what to expose, how to authenticate, and how to handle the inevitable schema drift. A well-designed endpoint saves a support ticket every time a downstream developer needs new fields, and it removes the temptation to bolt on a parallel Laravel microservice just to fetch a few nodes.
This walkthrough focuses on practical setup using Drupal core and a small set of contributed modules. You will see how to enable what is needed, define resources, control access, and document the contract so the next developer can pick it up without paging you. We will also cover the bits that bite people later: caching, rate limiting, and the Australian privacy obligations that apply when personally identifiable information starts flowing through your service.
Enabling REST and core dependencies
Before any code is written, a few modules need to be switched on. RESTful Web Services, Serialization, and HAL are the minimum stack for a JSON-friendly surface. Basic Auth is convenient for local development but rarely appropriate for production traffic. JSON:API is an alternative worth considering, especially if the external app can consume the full standardised schema; this guide, however, sticks with the classic REST module because it offers finer-grained control over individual endpoints.
Run the usual drush en command or use the Extend page in the admin toolbar. Once enabled, visit the REST configuration screen at /admin/config/services/rest and decide whether you want a single resource for everything or one resource per content type. For most external integrations, per-content-type resources are cleaner. A small council site in Hobart serving tourism data, for instance, might expose "attractions" and "events" as separate resources, each with its own allow list of methods.
Permissions live under the People section. Create a dedicated role for API consumers, grant only GET access on the resources you want exposed, and never reuse the administrator account for service-to-service traffic. The principle is identical to what the Australian Cyber Security Centre recommends for any system account: least privilege, dedicated identity, audited usage.
Shaping resources with REST UI
The REST UI contributed module provides a graphical editor for resources and saves you from hand-editing YAML files. Install it with Composer, enable it, and head to its admin page. Each resource has fields for supported formats, authentication providers, and granular permissions. If your downstream app only needs titles and body text, restrict the resource to those bundles rather than the entire content entity.
When the schema required by the external application does not line up with Drupal's internal field names, the serialization layer comes into play. Normalizers in Drupal convert entities into arrays, and you can influence the output by adjusting view modes or writing a small custom normalizer. The goal is a predictable payload: a partner in Parramatta building a kiosk app should never need to guess whether a field is field_event_date or eventDate. Pick a convention, document it, and stick to it.
A useful trick is to expose a meta block in each response with the site's environment, the resource version, and a timestamp. That single addition cuts down on confusion when a staging and production endpoint both reply 200 OK but contain different content during a release window.
Authentication choices for machine clients
Authentication is where many DIY endpoints fail. Drupal core supports Basic Auth, which is fine for development but sends credentials in every request, exposing them if transport security fails. Cookie auth is browser-only and useless for mobile clients. The realistic options for machine-to-machine traffic are OAuth 2.0 through the Simple OAuth module, or an API key gateway sitting in front of Drupal.
Simple OAuth implements the authorisation code and client credentials grants, which is exactly what most external integrations need. Generate a client, issue a scoped token, and store the secret in a secrets manager. For agencies hosting on AWS Sydney (ap-southeast-2), this pairs naturally with AWS Secrets Manager or Parameter Store. Tokens should have a short lifetime, and refresh flows should be tested before launch.
If your consumer is a third-party SaaS that cannot speak OAuth, a shared key passed through a custom header is sometimes the pragmatic compromise. Wrap that key in a middleware or a small custom module that checks for it before the request reaches the resource controller, and log every request for auditing. Under the Notifiable Data Breaches scheme that supplements the Privacy Act 1988, that audit trail becomes essential if a credential ever leaks.
Serialization, formatting, and stable contracts
A REST surface is only as useful as the JSON it returns. Drupal's serialization pipeline uses normalizers for each component of an entity, and you can chain them to produce exactly the structure your consumer expects. The HAL+JSON format is verbose but discoverable; plain JSON is leaner and what most teams ask for. Whichever you pick, be consistent across every endpoint, because a mobile team rebuilding their parser after every release will quickly lose patience.
Versioning belongs in the URL path, not in custom headers that are easy to forget. A path like /api/v1/events is honest about its contract, and /api/v2/events can ship breaking changes without retiring the old route overnight. Many Australian agencies keep v1 alive for twelve months after v2 ships, then send a final deprecation header before sunsetting it. This rhythm is friendly to partners and reduces the chance of an outage caused by an unannounced removal.
Field-level stability matters as much as endpoint versioning. If a field name changes, downstream parsers break. The safe habit is to treat each field as a published API element: do not rename it casually, and when you must, add the new name alongside the old one for a release cycle. Document every field with a description and example value. OpenAPI makes this easy; the OpenAPI for Drupal module generates a spec from your resource configuration, and that spec can be hosted on the site or pushed to a developer portal.
Caching, rate limiting, and performance
Drupal's page cache does not apply to authenticated REST responses by default, which is usually what you want for personalised data, but it leaves unauthenticated public reads unprotected. Switch on the dynamic page cache and configure cache contexts so each response varies only by what genuinely changes. A tourism endpoint serving public event listings in Cairns, for example, can cache aggressively and only invalidate when an editor publishes a new node.
Rate limiting is rarely built into Drupal core, so it usually lives in a reverse proxy. Varnish, NGINX, or a managed edge service can throttle requests per token or per IP. For sites behind Cloudflare or a similar Australian CDN, the rate limiting rules can be configured in the dashboard without touching the application. Aim for limits generous enough for legitimate burst traffic but tight enough to make scraping visibly expensive.
Monitoring closes the loop. Pipe Drupal's logging to a central platform, and alert on spikes in 4xx and 5xx responses. If you are running containers in ap-southeast-2, structured logs flowing into an Elasticsearch or Loki cluster make incident response much faster. The first time an external integration breaks at 02:00 AEST, you will be glad every endpoint has a dashboard.
Hardening, privacy, and ongoing maintenance
Security work does not stop once the endpoint is live. Review the resource list quarterly and remove anything no longer consumed. Rotate OAuth client secrets on a schedule, and keep an eye on Drupal security advisories because a core update can change serializer behaviour in ways that silently alter your payloads. A staging pipeline that runs contract tests against the live OpenAPI spec catches this early.
Privacy considerations deserve explicit attention when personal data is in play. The Australian Privacy Principles in the Privacy Act 1988 require that personal information be collected only by fair means, used for the purpose it was collected, and protected from unauthorised access. If your endpoint exposes user profiles, contributor data, or anything tied to an identifiable person, document the legal basis, log access, and consider field-level encryption for sensitive values. The Notifiable Data Breaches scheme means a leak of access tokens or personal fields may trigger an assessment and, where serious, a public notification.
Finally, treat the endpoint as a product. Write a short README for it, keep a changelog, and let the external team know before you change anything. A small piece of communication discipline is the difference between an endpoint that quietly serves a partner for years and one that becomes a source of friction after every release.
Practical habits that keep endpoints healthy over time:
- Track every consumer in a registry with contact details and current usage
- Run automated contract tests in CI against the published OpenAPI document
- Set calendar reminders for secret rotation and dependency reviews
- Tag stable responses with
Cache-ControlandETagheaders for efficient revalidation
Useful modules and tools worth pairing with a custom REST surface:
- JSON:API when the consumer can adopt a fully standardised contract
- OpenAPI for Drupal for always-up-to-date documentation
- Simple OAuth for proper token-based authentication
- RabbitMQ or Redis queue integration if writes from external apps need asynchronous processing