Skip to content

Thoughts on APIs

Naofumi Kagami edited this page Sep 11, 2022 · 3 revisions

API security and consolidation requirements

With the introduction of a BFF architecture, we think it is worthwhile to reconsider the role of APIs.

In a traditional SPA architecture, API design had to consider the following restrictions.

  1. APIs are sent directly to the browser. Therefore, any malicious JavaScript on the browser (injected through XSS vulnerabilities) can read anything on the API. As a result, you should be careful to never send sensitive information in the API.
  2. The SPA client will often be connected via a slow, high latency and unreliable network, over which the developer has not control. This is especially true for mobile. Therefore, care should be taken to minimise the size of the payload and the number of requests made. Consequently, we need to make sure we do not over-fetch and we also try to bundle information from different sources into a single response. Hence the desire for protocols like GraphQL.

In a BFF architecture, none of the above considerations are necessary.

  1. The API will be sent between the Next.js server and the Rails server. Both will be located in data centres connected via a very fast, low latency, reliable and secure network. In fact, the two may be co-located in the same data centre, in which case the network traffic will never leave the building.
  2. The Next.js server will also be physically secured. It will also not be susceptible to XSS attacks since it never runs code injected as content. It is generally considered safe to send sensitive information from your Rails app to the Next.js server.

Therefore, the API connecting the Next.js server and the Rails app can be designed without considering the recommended restrictions for SPA API. The API can be designed more like the protocol connecting your database to your Rails app.

On the other hand, in the current project, you will have to be aware of security in the following cases.

  1. In the case of SSR and SSG pages, the return value from getServerSideProps() and getStaticProps() will be sent as is to the browser. You need to ensure that the return values do not contain sensitive content. In other words, while the data transfer between the Next.js server and the Rails server is over a secure network, that between the Next.js server and the browser is not. Inside getServerSideProps() and getStaticProps(), you have to make sure that you do not transfer sensitive information to the browser. Essentially, we are simply transferring the task of filtering data from the back-end engineers working on the Rails server, to the front-end team working on the Next.js server. A good precaution would be to hand pick the information that want to send in the props inside getServerSideProps() and getStaticProps().
  2. When responding to non-GET requests, the response from the API will be sent as is to the browser. It will not go through getServerSideProps() or getStaticProps() and so there is no opportunity for the Next.js server to filter content.

In general, juggling the two types of APIs and their different requirements will be complicated and difficult to enforce. Hence the recommendation is to be on the safe side and to not send sensitive information from the Rails server unless absolutely necessary, regardless of whether it is intended for the Next.js server or for the browser.

On the other hand, consolidated, complex information is very unlikely to be sent in response to a non-GET request that does not go through getServerSideProps() or getStaticProps(). Customising the response from your Rails server to accommodate requirements from the front-end, will lead to increase coupling to the back-end. This will lead to cases where the front-end team needs to wait for the back-end to implement the customisation, cause inefficiencies, and undermine the benefits of separating the front-end from the back-end.

Do not hesitate to make multiple requests

We are worried that the various rendering modes of React (CSR/SSR/SSG etc.) are being misunderstood in a very important way. The general sense is that these are discrete technologies that are exclusive to each other. This is not true.

SSR rendered pages download fully formed HTML from the server on first load. However, when you click a link on an SSR page and transition to a different page on the same site, they you will render the second page using CSR technology. This is the same for a SSG page. What we think of as SSR/SSG, is actually a hybrid combined with CSR.

With SSR or SSG sites, you can also include embed useEffect hook so that after the initial fully formed HTML loads in the browser and is hydrated, you can send the server a request for additional information that you can use to update the page. This is another SSR/SSG CSR hybrid form. The difference compared to a regular CSR render is that with SSR/SSG, you start from a page that is already fully rendered, albeit with some information lacking. On the other hand, a regular CSR render starts with a blank page.

We use this to our advantage in the current project. To provide CSRF protection, we insert the CsrfToken component in forms. This generates an <input type="hidden" /> tag with the value set to the authenticity token. When it is first embedded in a SSR/SSG page downloaded as HTML from the server, the value will be blank. After hydration, the authenticity token will be fetched from the server via a useEffect hook, and inserted into the value of the input tag.

This technique will require multiple calls to the server, but it does not slow down initial page load. Our recommendation is, if multiple calls makes your code cleaner without slowing down the user experience, then go for it. Embrace the hybrid nature of CSR/SSR/SSG.

The number of APIs

When designing an API from scratch, we are more likely to be resource focused. For example, if you are working with a Category resource, you may have GET /categories (index), GET /categories/1 (show), POST /categories (create), PATCH /categories/1 (update), and DELETE /categories/1 (destroy). On the other hand, a Rails ERB application will additionally have views for edit and new.

When moving your application from ERB to React views, you have the option of using these extra endpoints to your advantage.

Assume for example that your show view is complex and also displays items that are contained in the Category. Hence your API for this endpoint will need to include these items.

On the other hand, your edit view may be much simpler and focus only on the Category. If you use the API for the show endpoint for this, then you will be loading the items as well and hence over-fetching.

One way of overcoming this is to design your API with switches to include or exclude items in the result. You may also use GraphQL. However, this is unnecessary if you use separate the show and edit endpoints, including items in show and excluding them in edit. Since you already have the endpoints for your ERB application, the extra effort is minimal.

API documentation

We are generally cautious about the need for fully documenting APIs for front-end development. We are only providing APIs for an internal team with which we have frequent communication. It seems like an unnecessary overhead to agree upon and document a formal API. The complexity of API documentation formats like Swagger do not help either.

Our recommendation is to quickly create JSON responses for all endpoints that you plan to transition to React. Use render json: [model] and include associated objects if necessary. Also prepare adequate seed data so that these endpoints return relevant data when accessed.

This should provide you with a working API that the front-end can use, very early in the project.

For non-GET requests, the front-end should simply look at the name attributes inside the input elements. This defines the API and the React forms and the ERB forms should look very much alike.

Summary

In summary, our recommendation is the following.

  1. Regardless of the enhanced security offered by the BFF architecture, do not send sensitive information from your Rails server unless absolutely necessary.
  2. Try not to do complex customisations on the Rails back-end responses to suit front-end requirements.