This series of blog post explains an approach to implementing ASP.NET Core Web API servers and .NET clients and provides an example implementation of a service that maps URL paths to information about corresponding entries in the Contentstack SaaS headless CMS. This entry requirements and considerations for the example Web API.
Solutions often need to determine the entry in the CMS that corresponds to a given URL path. For example, ASP.NET Core Razor Pages that use headless CMS may need to determine which entry to render based on the path in the URL requested by the visitor. Different customers use different solutions to map URLs to entries. Often, each entry contains a field to specify its URL path.
When accessing information about entries, it is often helpful to access entries about other entries related to that entry by URL hierarchy. For example, to construct the main navigation for a page such as /hr/jobs, we can include links to each of the siblings of /hr, highlight the /hr section of the navigation to indicate that jobs is in that section, and include the siblings and children of /hr/jobs in the navigation.
Contentstack does not provide an API that returns the entry associated with a URL or other entries related to that entry by URL hierarchy. Contentstack GraphQL may allow such a query, but for numerous reasons I would prefer to implement a search engine than use GraphQL.
In fact, I would prefer to query a search index than the CMS. A search index can contain documents/records for each entry in the CMS that has a URL, with fields for the content type, ID, and URL path. I can query the search index by any criteria including those that I may add in the future. Using such a search index also facilitates locating relations based on URL paths. The search index can store and return the JSON from the CMS to avoid a round-trip to the CMS to get the entries with the IDs returned by the search query.
Without a search index or GraphQL, to map a path to an entry, we could query each content type and return the first entry with a matching URL, but this would be inefficient, especially considering determination of entries related by URL path.
To implement a Web API that returns information about entries and their relatives by URL path, we can implement a cache that retrieves basic metadata about all entries that have URLs into a cache. This implementation stores that cache in application server memory, but it would be possible to use alternative caching infrastructure.
The API should accept an entry identifier (possibly a URL path or an entry ID, possibly including a content type) and return data about the entry including its content type, URL path, ID, and the ID of its parent entry, except for the home item for which there is no parent. When directed, the API should return he same properties of the ancestors, siblings, and descendants of the specified entry as determined by its URL path. To support generation of navigation elements, when directed, the API should include the navigation title of each entry. When directed, the API should return the entire JSON representation of each entry.
Returning data about ancestors and a single entry are not involve this risk but including information about siblings and descendants presents a significant concern. Your implicit assumption may be to model this as a hierarchy of entries. Any entry can have any number of siblings and descendants. For a site of any significant size, returning all the siblings and descendants of an entry in a single HTTP response is not practical, especially when including entry data. A hierarchical representation of the entry representing the home page would be like an export of all entries in the CMS that have URLs. There are several approaches to this issue.
- Returning the data as a flat list using ID references to reflect hierarchical structure is probably the easiest but depends on clients to assemble or otherwise represent hierarchies by paging through flat lists of results.
- Implementing limits to data creation such that any entry can have only a limited number of children and allow queries to retrieve only siblings and the first level of descendants requires CMS customizations such as validation and workflow publishing rules.
- Returning the entries as a hierarchy with facilities to page through each level (possibly only when results exceed the page size limits) requires clients to implement more complex paging logic.
- Generate warnings when approaching page or overall response size limits and throw errors after reaching them.
It is possible to use these options in combination, such as limiting data creation and sibling queries and throwing errors when approaching or exceeding limits. You could also use API parameters to give clients options. I expect to use this API mostly to map URLs to entries including ancestors, children, and siblings rather than deeper descendants, and I would avoid entries with dozens of children, but I do not want to demonstrate bad practices such as an API that could return too much data or throw errors. I chose to return the data as a flat list for paging. If the caller knows it only wants a single entry, it can avoid the paging logic and potentially throw an error if the API returns zero or multiple results.