Headless With Umbraco
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
Problem
Traditional agency development models are set up so that creative work moves from the design team, into the front-end development team to create markup and any JS functionality, and then to a C# developer to wire the site together and create the views. This is labour intensive often inefficient, which becomes even more pronounced when a UI change comes into the mix and the whole process has to be repeated. The timeline of the whole project is highly vulnerable if team resource schedules end up out of sync and misaligned.
Resourcing projects across an agency comes with a number of challenges. What should come first? Is feature 'X' dependent on feature 'Y' being developed? What if the client changes their mind, does it need to go back to the design team again? Hours can be spent on detailed calculations, and a matrix of gantt charts, with the project manager finally happy that we've thought of every possibility, allowing the resource to be scheduled. Only to find out that we can't map the resource to the plan; or a developer is on annual leave or sick; or a dependency emerges that the web design and front-end development teams need to happen first, but it can't be completed straight away. The output of which can then be 'sliced and diced'™ into HTML partials, added to Razor views and swapped out for loops and Document Types.
Front-end development speed is fast in part due to the tooling that is now available and the instant feedback received after making changes. Task runners (such as gulp.js, or webpack) can watch your workspace and when a change is detected rebuild the output for changes to be visualised immediately. This ability to make changes and see the output straight away means features can be developed fast.
Back when we used to write web services in Windows Communication Framework (WCF) there was this central tenet to development called Data Contracts. The idea being, that parties involved in using and providing the service would agree on a fixed implementation. Then all parties can go off and develop their application in the safe knowledge that everyone is working off an agreed upon data format. Interfaces for classes are a similar analogy.
Traditional Solutions
Either,
- Front Enders learn Razor and build directly into the Umbraco web solution. (FE move more towards BE)
- Back-end developers learn more front-end tooling and prepare more of the front end. Build more reusable components that can be quickly set up and configured. (BE move more towards FE)
Both of these approaches have drawbacks, pulling the development team into learning more about an area outside of their specialisms. This isn't an issue with the amount developers are constantly learning but, it does put constraints on the team that can may be avoided.
Can it be that there is another way?
A Headless CMS is a back-office only content management system (CMS) built as a content repository that makes content accessible via a RESTful API for display on any device. Other names frequently used such as Hybrid or Decoupled CMS which may also provide a default rendered view as part of the system as well as exposing an API.
This post is obviously about Headless...
There are many competitive products on the market, including - contentful, squidex and storyblok to name but a few. Creating an account and providing some simple content will immediately provide an API (REST or GraphQL) to retrieve this content. Rapid prototyping helps get to the bottom of key requirements. By following agile processes it’s much easier to get a minimum viable product (MVP) in front of a client so that you can have ‘real’ conversations about how a feature should look and work.
We love Umbraco though right!?
What if we could “switch” Umbraco into a Headless CMS (December 2nd the Headless product 'Umbraco Heartcore' was released as a "software as a service offering" running on Umbraco Cloud only - This article focuses on turning Umbraco on premise into a headless CMS)
It so happens that this isn’t that difficult to set up ourselves! The Matt Brailsford’s excellent HeadRest and AuthU package for API and authentication respectively can, with very little configuration take your Umbraco site and change the rendering engine to provide a REST API instead. Using AutoMapper for Umbraco v7 and UmbracoMapper for Umbraco v8 resolvers can be written to translate your content to flowing JSON!
We look at this approach ideally for highly bespoke websites, small in nature maybe with a lot of animation fitting the Single Page Application (SPA) model such as a tool or configurator.
public class Bootstrap : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase app, ApplicationContext ctx)
{
// Register CORS
GlobalConfiguration.Configure(WebApiConfig.Register);
// Register API Endpoint and set up routes
HeadRest.ConfigureEndpoint("/api/", new HeadRestOptions
{
CustomRouteMappings = new HeadRestRouteMap()
.For("^/(locations)/(?<location>[0-9]+)/?$").MapTo("/$1/")
.For("^/(houseTypes)/(?<location>[0-9]+)/?$").MapTo("/$1/")
.For("^/(floorTypes)/(?<housetype>[0-9]+)/?$").MapTo("/$1/"),
ViewModelMappings = new HeadRestViewModelMap()
.For(HomePage.ModelTypeAlias).MapTo<HomePageViewModel>()
.For(Locations.ModelTypeAlias).MapTo<LocationsViewModel>()
.For(HouseTypes.ModelTypeAlias).MapTo<HouseTypesViewModel>()
.For(FloorTypes.ModelTypeAlias).MapTo<FloorTypesViewModel>()
});
// Define mappings
Mapper.CreateMap<HomePage, HomePageViewModel>();
// Locations
Mapper.CreateMap<Locations, LocationsViewModel>()
.ForMember(dest => dest.Locations, opt => opt.ResolveUsing(new FilterResolver()));
}
}
Turns the response into...
{
"id": 1160,
"name": "Locations",
"locations": [{
"id": 1162,
"name": "Location 1",
"price": 285000,
"description": "<p><strong>Our flagship site for our custom-build properties based in Basingstoke.</strong></p>",
"image": "https://api.domain.com/media/1003/squirrel-wood-1000x750b.png",
"position": {
"latitude": 51.277129,
"longitude": -1.11617
},
"locationSlug": "/api/housetypes/1162"
}, {
"id": 1161,
"name": "Location 2",
"price": 310000,
"description": "<h4>A selection of 2, 3 and 4 bedroom custom-build homes in Swindon. </h4>",
"image": "https://api.domain.com/media/1882/mp-banner-cgi.png",
"position": {
"latitude": 55.406321,
"longitude": 10.387015
},
"locationSlug": "/api/housetypes/1161"
}]
}
We developed a house configurator which you can watch a short video explaining the tech stack (it's Headless!) and you can have a play - Demo (The site uses an Umbraco back-office, HeadRest for the API, Custom caching and AuthU package)
Some minor issues we ran into which I believe have been more ironed out now the libraries are more mature.
- CORS
There were a few issues to be overcome when working with an API on a different domain to the public website. The first is CORS which requires the domains in use to be added to a whitelist. There is a simple line of configuration code to add any domains required for this.
/// <summary>
/// Required for CORS configuration of API endpoints
/// </summary>
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var corsAttr = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(corsAttr);
}
}
- Caching
Out of the box there isn't any caching enabled, however you can easily add standard output caching by inheriting from the HeadRestController then override the Index action, adding the output cache directive. For GraphQL this is more of a limitation because the client is control of the query it's much harder to introduce a bespoke caching layer.
Headless Driven Development
But we can go even further, as there is still a certain amount of time required to set up the configuration of the API endpoints. If the mapping is more than just listing the Document Type properties, then you will need to write your own resolvers. This is just a fancy way of saying you need to write your own property mapping to be exposed by the API. For example, return all the children of a specific node stored using the Umbraco repository pattern....
What if there was a way of exposing your data via some kind of API that front-end developers (React/Vue) already know how to interact with and can retrieve whatever property/fields they need. Well, there is GraphQL and an Umbraco community package created by Rasmus John Pedersen, is currently in development using GraphQL for .NET port - I'm certainly not the first to speak about this [Pete Duncanson, Laura Weatherhead], as for this development workflow it is an ideal API to work with. To start with, there is just one endpoint. If we've missed out a field on a RESTful API we would need a C# developer to add this in - using HeadRest this is no more work than adding the field into the configuration/resolver. However, in GraphQL the front end developer can request the additional field just by changing the client side Select query. Once GraphQL is set up it can be entirely handed over to the front-end developers making Rapid Application Development highly possible. It minimises the dependency of one team being able to work before another has finished to the amount of time it takes to create enough Document Types the page or site requires which allows features to be iterated rapidly.
Summary
Whichever way you decide to expose an API endpoint, we've found that being able to utilise the robust, "friendly" CMS of Umbraco for the back-office but enable hand crafted, more animation heavy designs on the front end of a website, a winning combination. The agile nature of the development, allowing the creation of fast prototypes to present to the client for feedback on specific features saves enormous amounts of time and the client is able to think about these smaller feature elements at the correct time in the development process. The feedback from the client is far more specific and focused, and avoids the "grand reveal" syndrome at the end of the project where a client tries to process everything at once and give feedback on that basis.
I hope this has provided some food for thought and you'll think about utilising Headless Driven Development, and as we dive into December, I can wish you Merry Christmas! That mince pie has your name on it. Have a play and see what you think, I'd love to hear your thoughts!
David Challener
David is on Twitter as @firedave1