Skip to content

API Calls

Yichao Yu edited this page Jan 27, 2021 · 3 revisions

In Next.js pages and components, a lot of the code may run either on the client or the server. This is an major advantage for Next.js to reduce duplicated code, but it can also make it slightly harder when a different behavior on the server/client is desired. In some cases it is possible to tell where the code is running on, e.g. the UI event handler will only run on the client and some functions like getStaticProps will only run on the server. But in general, doing different things on the server/client requires explicitly detecting the environment, e.g. with process.browser.

A common and generalizable case is when calling the a server-side function when creating the UI. In this case, the input/output from the caller as well as the code that actually needs to run are exactly the same. The only difference is how the call can be routed (i.e. direct calling vs sending a POST request) so we should be able to abstract this out without having the caller doing anything special. (It may not be as simple as calling a normal function but the interface will be the same no matter where the code runs.)

The main challenge for this is that since webpack automatically loads/translates/compiles all code used by each page, we have to prevent it from grabbing server-only code that is impossible to run on the client side (e.g. it's accessing local file/database or calling a C library). Webpack provides an escape hatch for this which is to hide the require call behind a process.browser branch, i.e. we can create a client and server version of the module and let the user go through a wrapper module defined as,

module.exports = (process.browser ? require('client_module') : require('server_module'));

This makes sure the client and the server module are only ever being loaded on the client or server side respectively. (Note that we use this trick not only for the API calls but also for other functions that needs a different version on the client vs server, e.g. crypto/hash functions). We additionally configure webpack to not look into the server module by putting them in server/ and treating everything there as external modules that are not to be translated. We also force the module to be loaded as null on the client side to detect any possible errors.

The actual server API call interface is implemented by passing the arguments and results as objects. When calling from the server, the arguments and results are passed between the caller and the real API implementation directly whereas a caller from the client will pass the objects between the server and client via JSON string using a POST request on the /api path. Each API entry points has a name which is used as the key in the object for the argument and the result, i.e. to call the API api_name_1 one would do api({api_name_1: params_for_api}) and the returned value would have the form {api_name_1: result_for_api}. The caller can pass in an object with more than one fields to potentially call more than one functions with a single request to the server.

Each API corresponds to a module under apis/ which is automatically loaded by the server so that we don't need to manually construct a big lookup table to find the correct function by name.

In order to support sessions, the API implementations can access the request and the reply objects in order to read and modify cookies. For a direct server side call, as long as the call is always made as a response to a client request, these objects should be accessible from the Next.js API. For a client side request, these are directly filled in by the server code that handles the /api request.

Clone this wiki locally