Evolving Web: Building Decoupled Drupal – Part 2
In the first part of this article, we reviewed in detail the three main application types you should consider for building decoupled Drupal. Now, in part two, we’ll go through some key aspects that’ll determine how your application will work, and present some tips and resources to get your implementation started and help you make the best possible decisions.
💻 Blog post: Building Decoupled Drupal – Part 1: Choosing Your Application
Things to Consider When Implementing Decoupled Drupal
Routing is how an application determines the code that should handle a request based on the URL and can be one of the trickiest parts of decoupling. Fortunately, the headless Drupal community has made major advancements that make this part more manageable. So today, we effectively have two ways to address routing: We can manage it on the Drupal side, or we can manage it on the front-end.
In Drupal-managed routing, Drupal handles the logic and definitions for paths and menus, and content is retrieved using its Drupal-defined path.
Easier to manage URLs.
Menu management can be done in Drupal with no further front-end code changes.
Built-in multilingual support for paths.
The front-end must develop a flexible and dynamic way to render a page based on the entity in use.
Additional requests might be needed to retrieve a fully-loaded entity.
Building the menu requires additional requests to the CMS.
Front-end catch-all route
With content paths and menus managed by Drupal, the front-end will need to define a catch-all path where these Drupal-defined paths will be handled. A catch-all path will essentially process all requests to URLs that are not explicitly defined by the application. This is especially common when dealing with dynamic URLs. For example, in Nuxt.js, you can create a page component with the filename _.vue to handle unknown routes.
The catch-all page checks with Drupal to determine whether the given path exists. If it does exist, the catch-all page will retrieve the necessary content data to render the requested page. This can be done more easily with the help of the Decoupled Router module. Given a path, this module will return specific pieces of the associated entity’s information, such as its entity type and UUID; with this information, you can retrieve an entity’s full data using JSON:API.
You’ll usually end up with at least two steps in this case:
Resolve the path with the help of the Decoupled Router module
Retrieve the entity’s data via JSON:API
If you prefer, you can use the Subrequests module to combine these two steps into one.
The menus can also be built as defined by Drupal’s menu system. This allows editors and administrators to manage menu items via standard Drupal admin tools. JSON:API Menu Items allows custom menu entities to be returned via JSON:API.
In this approach, all routes are defined by the front-end and content is retrieved using a unique part of the URL, commonly known as a slug. Drupal must enforce the slug’s uniqueness in this scenario.
Since the front-end defines all the routes needed for the application, it is also responsible for ensuring that the right resources are called depending on the current path.
Front-end has full control over the routes.
Fewer requests are needed.
New paths mean code changes.
Multilingual support for paths must be built in the front end.
JSON:API can be considered the de facto standard in Drupal for exposing resources to external parties. It’s well-known, well-documented, and well-supported in the Drupal community.
Explicit Requests: Getting Only What You Need
Typically, the front-end will compose the request parameters to select the data needed to render the page. This is what I like to call explicit requests.
These requests are meant to be lean and therefore produce a concise response. The front-end can retrieve only the data it needs through sparse fieldsets, includes, and filters. However, this approach can feel unmanageable as new fields and relationships are added.
Lean requests result in lean responses.
Request parameters have to be manually and carefully built.
Implicit Requests: Automatically Including Everything
In cases where you want to include everything (i.e. the fully-loaded entity) in every response, you can use the JSON:API Defaults module; this is part of the JSON:API Extras module.
The JSON:API Defaults module lets you define the default include parameter, which will be used in any request that isn’t explicitly specified by the front-end. By doing this, the front-end is freed from manually adding all the necessary parameters to retrieve a fully-loaded entity, including its relationships.
This can be helpful, especially when you have deep and complex relationships. However, the trade-off is a potentially bloated response.
Default parameters are managed easily from the backend.
Previewing is another challenge for decoupled applications in general, not just in Drupal. Your options for previewing unpublished content depend greatly on the type of front-end application you’re building.
Staging Site / Staging Content
This approach works well with SSG web applications, where a front-end staging site is hosted independently and can be rebuilt via webhooks (e.g. Netlify). The Webhooks module allows you to define endpoints or HTTP callbacks that will be called when a particular event is dispatched (i.e. saving a node).
With some custom code, you can add controls to trigger the staging site’s webhook or production site’s webhook manually or automatically.
It requires a more involved and complicated implementation, but a more user-friendly approach is to preview the page from within Drupal itself. This most often involves using an iframe to embed the front-end application preview page in the Drupal admin and having a dedicated custom route on the front-end application for rendering previews. Obviously, security considerations have to be taken into account, and user access permissions should be checked and enforced.
👩💻 Check all of our upcoming Drupal training courses!
Custom resources – While JSON:API provides all the entity resources and CRUD operations out of the box, you will eventually find yourself needing a custom resource or endpoint for bespoke operations such as sending an email via a JSON:API resource. Check out the JSON:API Resources module for helpful tools for these situations.
Flexible image styles – With the help of the Consumer Image Styles module, you can include several image styles in the JSON:API response along with any image entity. This allows your front-end to decide which image style to use based on factors like the window size.
Disable unnecessary JSON:API resources – Consider using JSON:API Extras to enable only the resources you will need. Learn more about securing your site with JSON:API.
Sending multiple requests – Use Promise.all in the front-end when requests are independent of each other. Otherwise, consider using the Subrequests module to send a single request containing interdependent subrequests.
We’re Here to Help!
The possibilities of decoupled Drupal are exciting, but figuring out the best way to proceed can be intimidating. If you could use some support in taking your next steps toward decoupled Drupal, drop us a line and let’s chat!
Also, if you’re considering decoupled Drupal for your big migration from Drupal 7 to 9, check out these resources:
9 Frequently Asked Questions About Drupal 7 End of Life
Drupal 8/9 Migration: Migrating Basic Data
Our Top Drupal Modules for 2022