Developing REST/JSON:API Integration 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 preferable for headless architectures and mobile applications.
JSON:API — Enabling and First Requests
drush en jsonapi -y
After enabling, all content types are automatically available. URL format: /jsonapi/{entity_type}/{bundle}.
# List of articles
curl https://site.com/jsonapi/node/article
# Specific node by UUID
curl https://site.com/jsonapi/node/article/{uuid}
# Filtering
curl "https://site.com/jsonapi/node/article?filter[status]=1&filter[field_category.name]=News"
# Including related resources
curl "https://site.com/jsonapi/node/article?include=field_tags,uid"
# Sorting and pagination
curl "https://site.com/jsonapi/node/article?sort=-created&page[limit]=10&page[offset]=20"
# Selecting specific fields (sparse fieldsets)
curl "https://site.com/jsonapi/node/article?fields[node--article]=title,body,created"
Authentication for Writing
JSON:API supports GET without authentication (for public content). POST/PATCH/DELETE require authentication.
# Basic authentication (development only)
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" }
}
}
}'
For production — OAuth 2.0 via simple_oauth module:
composer require drupal/simple_oauth
drush en simple_oauth -y
Simple OAuth: Configuration
- Configuration → Simple OAuth → Generate keys
- Create OAuth Client: Configuration → Simple OAuth → Clients → Add Client
- Assign scopes to roles
# Get token (Client Credentials)
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"
REST API: Custom Resources
// src/Plugin/rest/resource/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 resource: Configuration → Web Services → REST → enable product_stock.
Custom JSON:API Filter
// Add computed field to JSON:API response
use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
// Via hook_jsonapi_resource_type_field_alter
function mymodule_jsonapi_resource_type_field_alter(array &$fields, EntityTypeInterface $entity_type, string $bundle): void {
if ($entity_type->id() === 'node' && $bundle === 'product') {
$fields['price_with_discount'] = ResourceTypeField::toPublicName('price_with_discount');
}
}
JSON:API Extras: Additional Configuration
composer require drupal/jsonapi_extras
drush en jsonapi_extras -y
JSON:API Extras allows:
- Disable endpoints for specific content types
- Change field names in response
- Add computed fields
- Configure API versioning
Response Caching
Drupal automatically caches JSON:API responses via Cache API with cache tags. When a node is updated, the cache for related requests is invalidated.
Add Varnish or Nginx FastCGI Cache before Drupal for maximum performance of public API endpoints.
Timeline
Basic JSON:API configuration with OAuth authentication — 2–3 days. Custom REST resources and JSON:API Extras — additional 2–3 days.







