Developing REST/JSON:API Integrations for Drupal
Drupal comes with two API systems out of the box: REST (flexible, requires configuration) and JSON:API (standardized, works immediately). JSON:API is preferred for headless architectures and mobile apps because it reduces API layer development time by 50% and provides a unified request format. However, developers frequently run into issues: incorrect authentication, N+1 queries when loading related entities, caching, and performance. In one project, we integrated a Drupal site with a mobile app. The requirement was fast loading of articles with tags and authors. Naive use of JSON:API resulted in many requests—for each article, tags and author were loaded separately. The solution was using the include parameter and caching configuration. After optimization, loading time dropped from 3 seconds to 200 milliseconds. The savings exceeded 40% of development time and reduced server load.
Why JSON:API is Better than REST in Drupal
| Characteristic | REST | JSON:API |
|---|---|---|
| Standardization | No, each resource its own endpoints | Strict JSON:API standard |
| Including relationships | Manually via ?include= |
Automatically via include |
| Filtering | Custom query parameters | Standard filters |
| Pagination | Not standardized | Standard page[limit], page[offset] |
| Versioning | Absent | Via Content-Type or headers |
| Performance | Requires manual caching | Automatic caching with cache tags |
Learn more about the JSON:API standard on Wikipedia. JSON:API wins in typical headless architecture scenarios. It reduces API layer development time and provides a unified request format.
How to Configure JSON:API and Authentication
Enable JSON:API via Drush:
drush en jsonapi -y
Once enabled, all content types are automatically available. URL format: /jsonapi/{entity_type}/{bundle}.
Example request with filtering, including relationships, and sparse fieldsets:
curl https://site.com/jsonapi/node/article?filter[status]=1&include=field_tags,uid&sort=-created&page[limit]=10&page[offset]=20&fields[node--article]=title,body,created
Writing requires authentication. Basic authentication is suitable for development; use OAuth 2.0 in production.
# POST with basic authentication
curl -X POST https://site.com/jsonapi/node/article -u admin:password -H "Content-Type: application/vnd.api+json" -d '{"data":{"type":"node--article","attributes":{"title":"New article","body":{"value":"<p>Text</p>","format":"full_html"}}}}'
Configure OAuth 2.0 with the simple_oauth module:
composer require drupal/simple_oauth
drush en simple_oauth -y
# Generate keys and create a client
# Get a token
curl -X POST https://site.com/oauth/token -d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=editor"
# Request with Bearer token
curl https://site.com/jsonapi/node/article -H "Authorization: Bearer ACCESS_TOKEN"
Custom REST Resources for Non‑Standard Tasks
If JSON:API is insufficient, create a custom REST resource. For example, an endpoint to check product stock by SKU.
Example implementation of ProductStockResource
<?php
namespace Drupal\mymodule\Plugin\rest\resource;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* @RestResource(
* id = "product_stock",
* label = @Translation("Product Stock"),
* uri_paths = {
* "canonical" = "/api/products/{sku}/stock",
* "create" = "/api/products/stock/update"
* }
* )
*/
class ProductStockResource extends ResourceBase {
public function get(string $sku): ResourceResponse {
$node = $this->getProductBySku($sku);
if (!$node) {
throw new NotFoundHttpException("Product $sku not found");
}
$response = new ResourceResponse([
'sku' => $sku,
'stock' => (int) $node->get('field_stock_quantity')->value,
'available' => (bool) $node->get('field_in_stock')->value,
]);
$response->addCacheableDependency($node);
return $response;
}
public function patch(array $data): ResourceResponse {
$sku = $data['sku'] ?? throw new BadRequestHttpException('SKU required');
$node = $this->getProductBySku($sku);
$node->set('field_stock_quantity', $data['quantity']);
$node->save();
return new ResourceResponse(['updated' => true], 200);
}
}
Enable the resource in the admin: Configuration → Web Services → REST.
How to Optimize API Performance
Main Drupal API performance issues: N+1 queries, lack of caching, inefficient field selection. Use the following techniques:
- Including relationships (
include) to reduce the number of queries. - Sparse fieldsets—select only needed fields via the
fieldsparameter. - Caching—configure cache tags and use Varnish or FastCGI Cache.
- Database indexes—create indexes for fields that are frequently filtered.
| Technique | Effect |
|---|---|
| include | Reduces queries from 1+N+M to 1 |
| Sparse fieldsets | Reduces response size up to 3x |
| Cache tags | Eliminates repeated requests to paths |
| Indexes | Speeds up filtering by 10-100x |
Example optimization: when loading a list of articles with authors and tags, without include there are 1+N+M queries (1 for list, N for authors, M for tags). With include—one query. In a real project, this reduced response time from 3 to 0.2 seconds.
What's Included in Our Work
- Audit of current architecture and selection of the appropriate API approach (REST/JSON:API/GraphQL)
- Configuration of JSON:API with OAuth 2.0, filtering, relationship inclusion, pagination
- Development of custom REST resources for non‑standard scenarios
- Performance optimization: caching, cache tags, Varnish/FastCGI
- API documentation in OpenAPI format
- Client team training on working with the API
- Launch support (2 weeks of free support after launch)
Process
- Analysis—we study your architecture, load, requirements.
- Design—we select the stack (JSON:API or REST), design endpoints.
- Implementation—code, configure authentication, caching.
- Testing—load testing, checking for N+1, cache.
- Deployment—roll out to production, monitoring.
- Support—bug fixes, consultations.
Timeline and Cost
Basic JSON:API setup with OAuth—from 2 to 4 days. Custom REST resources—from 2 to 5 days. Cost is calculated individually after an audit. We guarantee stable API operation under loads up to 1000 requests per second.
Guarantees
- 5+ years of experience—delivered 20+ Drupal integrations
- Certified Drupal and Symfony specialists
- Code warranty—6 months of free fixes
- N+1 Query Prevention—no more than 4 SQL queries per page on all requests
Get Your Project Evaluated
Contact us for a consultation—we will analyze your task and propose the optimal solution. Order a turnkey integration development: get a ready API with documentation and support.







