{"id":90021,"date":"2021-02-22T18:28:34","date_gmt":"2021-02-22T18:28:34","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=90021"},"modified":"2022-04-24T21:22:39","modified_gmt":"2022-04-24T21:22:39","slug":"integrate-create-react-app-with-net-core-5","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/integrate-create-react-app-with-net-core-5\/","title":{"rendered":"Integrate Create React app with .NET Core 5"},"content":{"rendered":"<p>The <em>Create React <\/em>app is the community\u2019s preferred way to spin up a brand new React project. This tool generates the basic scaffold to start writing code and abstracts away many challenging dependencies. React tools like webpack and Babel are lumped into a single dependency. React developers can focus on the problem at hand, which lowers the bar necessary to build Single Page Apps.<\/p>\n<p>The question remains, React solves the problem on the client-side, but what about the server-side? .NET developers have a long history of working with Razor, server-side configuration, and the ASP.NET user session via a session cookie. In this take, I will show you how to get the best of both worlds with a nice integration between the two.<\/p>\n<p>This article has a hands-on approach that reads better from top to bottom so you can follow along. If you feel comfortable enough with the code, it is <a href=\"https:\/\/github.com\/beautifulcoder\/integrate-dotnet-core-create-react-app.git\">available on GitHub<\/a> for your reading pleasure.<\/p>\n<p>The general solution involves two major pieces, the front and the back ends. The back end is a typical ASP.NET MVC app anyone can spin up in .NET Core 5. Just make sure that you have .NET Core 5 installed and that your project is targeting .NET Core 5 when you do this to unlock C# 9 features. I will incrementally add more pieces as the integration progresses. The front end will have the React project, with an NPM build that outputs static assets like <em>index.html<\/em>. I will assume a working knowledge of .NET and React, so I don\u2019t have to delve into basics such as setting up .NET Core or Node on a dev machine. That said, note some of the useful using statements here for later use:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using Microsoft.AspNetCore.Http;\r\nusing System.Net;\r\nusing Microsoft.AspNetCore.Authentication.Cookies;\r\nusing Microsoft.AspNetCore.Authorization;\r\nusing System.Text.RegularExpressions;     <\/pre>\n<h2>Initial Project Setup<\/h2>\n<p>The good news is that Microsoft provides a basic scaffold template that will spin up a new ASP.NET project with React on the front end. This ASP.NET React project has the client app, which outputs static assets that can be hosted anywhere, and the ASP.NET back end that gets called via API endpoints to get JSON data. The one advantage here is that the entire solution can be deployed at the same time as one monolith without splitting both ends into separate deployment pipelines.<\/p>\n<p>To get the basic scaffold in place:<\/p>\n<pre class=\"lang:c# theme:vs2012\">mkdir integrate-dotnet-core-create-react-app\r\ncd integrate-dotnet-core-create-react-app\r\ndotnet new react --no-https\r\ndotnet new sln\r\ndotnet sln add integrate-dotnet-core-create-react-app.csproj<\/pre>\n<p>With this in place, feel free to open the solution file in Visual Studio or VS Code. You can fire up the project with <code>dotnet run<\/code> to see what the scaffold does for you. Note the command <code>dotnet new react<\/code>; this is the template I\u2019m using for this React project.<\/p>\n<p>Here is what the initial template looks like:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1244\" height=\"914\" class=\"wp-image-90022\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-134.png\" \/><\/p>\n<p>If you run into any issues with React, simply change directory into <code>ClientApp<\/code> and run <code>npm<\/code> <code>install<\/code> to get Create React App up and running. The entire app renders on the client without server-side rendering. It has a <code>react-router<\/code> with three different pages: a counter, one that fetches weather data, and a home page. If you look at the controllers, it has a <code>WeatherForecastController<\/code> with an API endpoint to get weather data.<\/p>\n<p>This scaffold already includes a Create React App project. To prove this, open the <em>package.json<\/em> file in the <em>ClientApp<\/em> folder to inspect it.<\/p>\n<p>This is what gives it away:<\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n  \"scripts\": {\r\n      \"start\": \"rimraf .\/build &amp;&amp; react-scripts start\",\r\n      \"build\": \"react-scripts build\",\r\n    }\r\n}<\/pre>\n<p>Look for <em>react-scripts<\/em>; this is the single dependency that encapsulates all other React dependencies like webpack . To upgrade React and its dependencies in the future, all you need to do is upgrade this one dependency. This is the React App&#8217;s real power because it abstracts away an otherwise potentially hazardous upgrade to stay on the latest bits.<\/p>\n<p>The overall folder structure follows a conventional Create React App project in <em>ClientApp<\/em> with an ASP.NET project wrapped around it.<\/p>\n<p>The folder structure looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"372\" height=\"748\" class=\"wp-image-90023\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-135.png\" \/><\/p>\n<p>This React app comes with many niceties but lacks some important ASP.NET features:<\/p>\n<ul>\n<li>there is no server-side rendering via Razor, making any other MVC page work like a separate app<\/li>\n<li>ASP.NET server configuration data is hard to access from the React client<\/li>\n<li>it does not integrate with an ASP.NET user session via a session cookie<\/li>\n<\/ul>\n<p>I will tackle each one of these concerns as I progress through the integration. What\u2019s nice is these desirable features are attainable with the Create React App and ASP.NET.<\/p>\n<p>To keep track of integration changes, I will now use Git to commit the initial scaffold. Assuming Git is installed, do a <code>git<\/code> <code>init<\/code>, <code>git<\/code> <code>add<\/code>, and <code>git<\/code> <code>commit<\/code> to commit this initial project. Looking at the commit history is an excellent way to track what changes are necessary to do the integration.<\/p>\n<p>Now, create the following folders and files that are useful for this integration. I recommend using Visual Studio with a right-click create Controller, Class, or View:<\/p>\n<ul>\n<li><strong>\/Controllers\/HomeController.cs<\/strong>: server-side home page that will override Create React App\u2019s <strong>index.html<\/strong> entry page<\/li>\n<li><strong>\/Views\/Home\/Index.cshtml<\/strong>: Razor view to render server-side components and a parsed <strong>index.html<\/strong> from the React project<\/li>\n<li><strong>\/CreateReactAppViewModel.cs<\/strong>: main integration view model that will grab the <strong>index.html<\/strong> static asset and parse it out for MVC consumption<\/li>\n<\/ul>\n<p>Once these folders and files are in place, kill the current running app, and spin up the app in watch mode via <code>dotnet<\/code> <code>watch<\/code> <code>run<\/code>. This command keeps track of changes both on the front and back ends and even refreshes the page when it needs to.<\/p>\n<p>The rest of the necessary changes will go in existing files that were put in place by the scaffold. This is great because it minimizes code tweaks necessary to flesh out this integration.<\/p>\n<p>Time to roll up your sleeves, take a deep breath, and tackle the main part of this integration.<\/p>\n<h2>CreateReactAppViewModel integration<\/h2>\n<p>I will begin by creating a view model that does most of the integration. Crack open <code>CreateReactAppViewModel<\/code> and put this in place:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class CreateReactAppViewModel\r\n{\r\n  private static readonly Regex _parser = new(\r\n    @\"&lt;head&gt;(?&lt;HeadContent&gt;.*)&lt;\/head&gt;\\s*&lt;body&gt;(?&lt;BodyContent&gt;.*)&lt;\/body&gt;\",\r\n    RegexOptions.IgnoreCase | RegexOptions.Singleline);\r\n\r\n  public string HeadContent { get; set; }\r\n  public string BodyContent { get; set; }\r\n\r\n  public CreateReactAppViewModel(HttpContext context)\r\n  {\r\n    var request = WebRequest.Create(\r\n      context.Request.Scheme + \":\/\/\" + context.Request.Host +\r\n      context.Request.PathBase + \"\/index.html\");\r\n\r\n    var response = request.GetResponse();\r\n    var stream = response.GetResponseStream();\r\n    var reader = new StreamReader(\r\n      stream ?? throw new InvalidOperationException(\r\n        \"The create-react-app build output could not be found in \" +\r\n        \"\/ClientApp\/build. You probably need to run npm run build. \" +\r\n        \"For local development, consider npm start.\"));\r\n\r\n    var htmlFileContent = reader.ReadToEnd();\r\n    var matches = _parser.Matches(htmlFileContent);\r\n\r\n    if (matches.Count != 1)\r\n    {\r\n      throw new InvalidOperationException(\r\n        \"The create-react-app build output does not appear \" +\r\n        \"to be a valid html file.\");\r\n    }\r\n\r\n    var match = matches[0];\r\n\r\n    HeadContent = match.Groups[\"HeadContent\"].Value;\r\n    BodyContent = match.Groups[\"BodyContent\"].Value;\r\n  }\r\n}<\/pre>\n<p>This code may seem scary at first but only does two things: gets the output <strong>index.html<\/strong> file from the dev server and parses out the <code>head<\/code> and <code>body<\/code> tags. This allows the consuming app in ASP.NET to access the HTML that links to the static assets that come from Create React App. The assets will be the static files that contain the code bundles with JavaScript and CSS in it. For example, <em>js\\main.3549aedc.chunk.js<\/em> for JavaScript or <em>css\\2.ed890e5e.chunk.css<\/em> for CSS. This is how webpack takes in the React code that is written and presents it to the browser.<\/p>\n<p>I opted to fire a <code>WebRequest<\/code> to the dev server directly because Create React App does not materialize any actual files accessible to ASP.NET while in developing mode. This is sufficient for local development because it works well with the webpack dev server. Any changes on the client-side will automatically update the browser. Any back-end changes while in watch mode will also refresh the browser. So, you get the best of both worlds here for optimum productivity.<\/p>\n<p>In prod, this will create static assets via <code>npm<\/code> <code>run<\/code> <code>build<\/code>. I recommend doing file IO and reading the index file off its actual location in <em>ClientApp\/build<\/em>. Also, while in prod mode, it is a good idea to cache the contents of this file after the static assets have been deployed to the hosting server.<\/p>\n<p>To give you a better idea, this is what a built <strong>index.html<\/strong> file looks like:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"762\" height=\"572\" class=\"wp-image-90024\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-136.png\" \/><\/p>\n<p>I\u2019ve highlighted the head and body tags the consuming ASP.NET app needs to parse. Once it has this raw HTML, the rest is somewhat easy peasy.<\/p>\n<p>With the view model in place, time to tackle the home controller that will override the <em>index.html<\/em> file from React.<\/p>\n<p>Open the <code>HomeController<\/code> and add this:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class HomeController : Controller\r\n{\r\n  public IActionResult Index()\r\n  {\r\n    var vm = new CreateReactAppViewModel(HttpContext);\r\n\r\n    return View(vm);\r\n  }\r\n}<\/pre>\n<p>In ASP.NET, this controller will be the default route that overrides Create React App with server-side rendering support. This is what unlocks the integration, so you get the best of both worlds.<\/p>\n<p>Then, put this Razor code in <em>Home\/Index.cshtml<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">@model integrate_dotnet_core_create_react_app.CreateReactAppViewModel\r\n\r\n&lt;!DOCTYPE html&gt;\r\n&lt;html lang=\"en\"&gt;\r\n&lt;head&gt;\r\n  @Html.Raw(Model.HeadContent)\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n  @Html.Raw(Model.BodyContent)\r\n\r\n  &lt;div class=\"container \"&gt;\r\n    &lt;h2&gt;Server-side rendering&lt;\/h2&gt;\r\n  &lt;\/div&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>The React app uses <code>react-router<\/code> to define two client-side routes. If the page gets refreshed while the browser is on a route other than home, it will revert to the static <em>index.html<\/em>.<\/p>\n<p>To address this inconsistency, define these server-side routes in <code>Startup<\/code>. Routes are defined inside <code>UseEndpoints<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">endpoints.MapControllerRoute(\r\n  \"default\",\r\n  \"{controller=Home}\/{action=Index}\");\r\nendpoints.MapControllerRoute(\r\n  \"counter\",\r\n  \"\/counter\",\r\n  new { controller = \"Home\", action = \"Index\"});\r\nendpoints.MapControllerRoute(\r\n  \"fetch-data\",\r\n  \"\/fetch-data\",\r\n  new { controller = \"Home\", action = \"Index\"});<\/pre>\n<p>With this, take a look at the browser which will now show this server-side \u201ccomponent\u201d via an <strong>h3<\/strong> tag. This may seem silly because it\u2019s just some simple HTML rendered on the page, but the possibilities are endless. The ASP.NET Razor page can have a full app shell with menus, branding, and navigation that can be shared across many React apps. If there are any legacy MVC Razor pages, this shiny new React app will integrate seamlessly.<\/p>\n<h2>Server-Side App Configuration<\/h2>\n<p>Next, say I want to show server-side configuration on this app from ASP.NET, such as the HTTP protocol, hostname, and the base URL. I chose these mostly to keep it simple, but these config values can come from anywhere. They can be <em>appsettings.json<\/em> settings or even values from a configuration database.<\/p>\n<p>To make server-side settings accessible to the React client, put this in <strong>Index.cshtml<\/strong>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;script&gt;\r\n  window.SERVER_PROTOCOL = '@Context.Request.Protocol';\r\n  window.SERVER_SCHEME = '@Context.Request.Scheme';\r\n  window.SERVER_HOST = '@Context.Request.Host';\r\n  window.SERVER_PATH_BASE = '@Context.Request.PathBase';\r\n&lt;\/script&gt;\r\n\r\n&lt;p&gt;\r\n  @Context.Request.Protocol\r\n  @Context.Request.Scheme:\/\/@Context.Request.Host@Context.Request.PathBase\r\n&lt;\/p&gt;<\/pre>\n<p>This sets any config values that come from the server in the global <code>window<\/code> browser object. The React app can retrieve these values with little effort. I opted to render these same values in Razor, mostly to show they are the same values that the client app will see.<\/p>\n<p>In React, crack open <em>components\\NavMenu.js<\/em> and add this snippet; most of this will go inside the <code>Navbar<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">import { NavbarText } from 'reactstrap';\r\n\r\n&lt;NavbarText&gt;\r\n  {window.SERVER_PROTOCOL}\r\n   {window.SERVER_SCHEME}:\/\/{window.SERVER_HOST}{window.SERVER_PATH_BASE}\r\n&lt;\/NavbarText&gt;<\/pre>\n<p>The client app will now reflect the server configuration that is set via the global <code>window<\/code> object. There is no need to fire an Ajax request to load this data or somehow make it available to the <em>index.html<\/em> static asset.<\/p>\n<p>If you\u2019re using Redux, for example, this is even easier because this can be set when the app initializes the store. Initial state values can be passed into the store before anything renders on the client.<\/p>\n<p>For example:<\/p>\n<pre class=\"lang:c# theme:vs2012 \">const preloadedState = {\r\n  config: {\r\n    protocol: window.SERVER_PROTOCOL,\r\n    scheme: window.SERVER_SCHEME,\r\n    host: window.SERVER_HOST,\r\n    pathBase: window.SERVER_PATH_BASE\r\n  }\r\n};\r\n\r\nconst store = createStore(reducers, preloadedState, \r\n    applyMiddleware(...middleware));<\/pre>\n<p>I chose to opt-out of putting in place a Redux store for the sake of brevity, but this is a rough idea of how it can be done via the <code>window<\/code> object. What\u2019s nice with this approach is that the entire app can remain unit-testable without polluting it with browser dependencies like this <code>window<\/code> object.<\/p>\n<h2>.NET Core user session integration<\/h2>\n<p>Lastly, as the pi\u00e8ce de r\u00e9sistance, I will now integrate this React app with the ASP.NET user session. I will lock down the back-end API where it gets weather data and only show this information with a valid session. This means that when the browser fires an Ajax request, it must contain an ASP.NET session cookie. Otherwise, the request gets rejected with a redirect which indicates to the browser it must first login.<\/p>\n<p>To enable user session support in ASP.NET, open the <em>Startup<\/em> file and add this:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public void ConfigureServices(IServiceCollection services)\r\n{\r\n  services\r\n    . AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)\r\n    .AddCookie(options =&gt;\r\n    {\r\n      options.Cookie.HttpOnly = true;\r\n    });\r\n}\r\n\r\npublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n{\r\n  \/\/ put this between UseRouting and UseEndpoints\r\n  app.UseAuthentication();\r\n  app.UseAuthorization();\r\n}<\/pre>\n<p>Be sure to leave the rest of the scaffold code in there and only add this snippet in the correct methods. With authentication\/authorization now enabled, go to the <code>WeatherForecastController<\/code> and slap an <code>Authorize<\/code> attribute to the controller. This will effectively lock it down to where it needs an ASP.NET user session via a cookie to get to the data.<\/p>\n<p>The <code>Authorize<\/code> attribute assumes the user can login into the app. Go back to the <code>HomeController<\/code> and add the login\/logout methods. Remember to be using <code>Microsoft<\/code>.<code>AspNetCore<\/code>.<code>Authentication<\/code>, <code>Microsoft<\/code>.<code>AspNetCore<\/code>.<code>Authentication<\/code>.<code>Cookies<\/code>, and <code>Microsft<\/code>.<code>AspNetCore<\/code>.<code>Mvc<\/code>.<\/p>\n<p>This is one way to establish and then kill the user session:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public async Task&lt;ActionResult&gt; Login()\r\n{\r\n  var userId = Guid.NewGuid().ToString();\r\n  var claims = new List&lt;Claim&gt;\r\n  {\r\n    new(ClaimTypes.Name, userId)\r\n  };\r\n\r\n  var claimsIdentity = new ClaimsIdentity(\r\n    claims,\r\n    CookieAuthenticationDefaults.AuthenticationScheme);\r\n  var authProperties = new AuthenticationProperties();\r\n\r\n  await HttpContext.SignInAsync(\r\n    CookieAuthenticationDefaults.AuthenticationScheme,\r\n    new ClaimsPrincipal(claimsIdentity),\r\n    authProperties);\r\n\r\n  return RedirectToAction(\"Index\");\r\n}\r\n\r\npublic async Task&lt;ActionResult&gt; Logout()\r\n{\r\n  await HttpContext.SignOutAsync(\r\n    CookieAuthenticationDefaults.AuthenticationScheme);\r\n\r\n  return RedirectToAction(\"Index\");\r\n}<\/pre>\n<p>Note that the user session is typically established with a redirect and an ASP.NET session cookie. I added a <code>ClaimsPrincipal<\/code> with a user-id set to a random <strong>Guid<\/strong> to make this seem more real. In a real app, these claims can come from a database or a JWT.<\/p>\n<p>To expose this functionality to the client, open <em>components\\NavMenu.js<\/em> and add these links to the <code>Navbar<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;NavItem&gt;\r\n  &lt;a class=\"text-dark nav-link\" href=\"\/home\/login\"&gt;Log In&lt;\/a&gt;\r\n&lt;\/NavItem&gt;\r\n&lt;NavItem&gt;\r\n  &lt;a class=\"text-dark nav-link\" href=\"\/home\/logout\"&gt;Log Out&lt;\/a&gt;\r\n&lt;\/NavItem&gt;<\/pre>\n<p>Lastly, I want the client app to handle request failures and give some indication to the end user that something went wrong. Bust open <em>components\\FetchData.js<\/em> and replace <code>populateWeatherData<\/code> with this code snippet:<\/p>\n<pre class=\"lang:c# theme:vs2012\">async populateWeatherData() {\r\n  try {\r\n    const response = await fetch(\r\n      'weatherforecast',\r\n      {redirect: 'error'});\r\n    const data = await response.json();\r\n    this.setState({ forecasts: data, loading: false });\r\n  } catch {\r\n    this.setState({\r\n      forecasts: [{date: 'Unable to get weather forecast'}],\r\n      loading: false});\r\n  }\r\n}<\/pre>\n<p>I tweaked the <code>fetch<\/code> so it does not follow failed requests on a redirect, which is an error response. The ASP.NET middleware responds with a redirect to the login page when an Ajax request fails to get the data. In a real app, I recommend customizing this to a 401 (Unauthorized) status code so the client can deal with this more gracefully. Or, set up some way to poll the back end and check for an active session and redirect accordingly via <code>window.location<\/code>.<\/p>\n<p>Done, the dotnet watcher should keep track of changes on both ends while refreshing the browser. To take this out for a test spin, I will first visit the Fetch Data page, note that the request fails, login, and try again to get weather data with a valid session. I will open the network tab to show Ajax requests in the browser.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-90025\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-137.png\" alt=\"Integrate Create React app with .NET core 5\" width=\"1504\" height=\"1068\" \/><\/p>\n<p>Note the 302 redirect when I first get the weather data, and it fails. Then, the subsequent redirect from login establishes a session. Peeking at the browser cookies shows this cookie name <code>AspNetCore<\/code><strong>.<\/strong><code>Cookies<\/code>, which is a session cookie that allows a subsequent Ajax request to work properly.<\/p>\n<h2>Integrate Create React app with .NET Core 5<\/h2>\n<p>.NET Core 5 and React do not have to live in separate silos. With an excellent integration, it is possible to unlock server-side rendering, server config data, and the ASP.NET user session in React.<\/p>\n<p><em>If you liked this article, you might also like\u00a0<a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/net-tools\/working-with-react-components\/\">Working with React Components.<\/a><\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Camilo Reyes demonstrates how to integrate the Create React app with .NET core to generate a scaffold which removes several dependencies.&hellip;<\/p>\n","protected":false},"author":274017,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[137090,137089,123655],"coauthors":[41241],"class_list":["post-90021","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net-core-5","tag-create-react-app","tag-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/90021","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/274017"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=90021"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/90021\/revisions"}],"predecessor-version":[{"id":90029,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/90021\/revisions\/90029"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=90021"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=90021"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=90021"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=90021"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}