Skip to main content

Advanced recipes

Various recipes regarding GraphQL, building etc. for advanced use cases. Vulcan is also about learning some magic!

Magic imports​

  • Use the paths property of tsconfig.common.json.
  • Add an alias in Next's Webpack config
  • Add an alias in Jest moduleNameMapper config

Relevant documentation

Why 2-steps?​

Using TypeScript paths will only tell TypeScript where to find the code. However, it won't replace your import path at build time.

So, you also need Webpack to actually replace ~/components/myComponent to ../../../components/myComponent.

Setting a cron job​

Next is a serverless framework, so API routes like api/graphql are short-lived functions. There is no centralized place where you can setup cron jobs. Even in a monolith like Meteor, cron jobs can raise issues if you run multiple instances of the app.

There are alternate patterns:

  • You may create an API route in Next that does what your cron job do, and call it from a GitHub Action or equivalent. See Vercel docs
  • You may create a separate, long-lived "satellite" Express micro-server, just for those use cases
  • You can write scripts in TypeScript and build them with ncc, we have an example in the "/scripts" folder. It's however experimental. Then you can run the script and setup the cron job directly on a virtual machine somewhere.

Debugging Cypress build​

See Cypress Webpack Preprocessor doc, it describes a few environment variables useful to debug Cypress build.

Mock next packages​

Use a Webpack alias.

Connect to multiple graphql API in the frontend​

Schema stitching?​

Handling connections to multiple graphQL APIs is the nightmare of the modern frontend developer.

  • Your backend team may not be able to provide a unique API either. So you often need a way to do schema stitching client side.
  • Most documentations about schema stitching (client-side) assume that you own the schema (meaning you can get it locally), and that of course it's written in JS. But real frontend developers, most often, connect to APIs they don't own, probably written in a various set of languages...
  • You can do stitching of remote schemas, but documentation is rather terse.

See GraphQL tools documentation for more infos on remote schemas and stitching.

Multiple Apollo clients?​

No, you don't do that. That messes up everything. EVERYTHING. Cache, optimistic UI, development tools... See the section just below for a cleaner, simpler pattern.

But there is one neat trick that "Adepts of the schema stitch" don't want you to know!

It is described in this article from Loud noises. One user, @naveennazimudeen, proposed an extended version in the comments of said article to handle N services.

The idea is to use directionnal composition from Apollo Link. That's a frightening term to say that you can replace a link depending on the context. Let's see the code:

// Using Apollo v2, but it should extend to newer versions
import { split } from "apollo-link"
// Compute the HTTP link depending on the context
const getHttpLink = (operation) => {
const service = operation.getContext().service;
let uri= "";
if (!service || service === "main") uri = "http://localhost:3000/api/graphql";
if (service === "user") uri = "http://my-user-service.whatever";
if (service === "vulcan") uri = "http://localhost:3001/graphql"
const link = httpLink(uri);
return link.request(operation);
};

// Trick to be able to load any number of links using split, by calling a function
const multiUriHttpLink = split(
() => true, // force to call the first (and only) branch of the split
operation => getHttpLink(operation, ctx) // by using a function, we allow to split between any number of links
);

// and then in your ApolloClient construction:
...
link: from([errorLink, multiUriHttpLink]),
...

And then you can swap the service like so:

// usage
const { data, loading, error } = useQuery(MY_QUERY, {
context: { service: "service1" },
});

Main limitation is that you can call services only one by one. This pattern is not the recommended way to call multiple APIs, it has clear limitations. But it does the trick , especially if you need to quickly connect to many 3rd party libraries in isolated parts of your app.

Since we use the same client and only change a link, the Apollo cache is shared between services. DevTools should still work with this approach.

Test server-only code​

Approach 1: change the testEnvironment on the fly​

jsdom (=client) is the default testEnvironment option for Jest. When testing your app server-specific code, you may want to use the node environment.

You can do so for a specific test file by adding this at the top:

/**
* // @see https://jestjs.io/docs/en/next/configuration#testenvironment-string
* @jest-environment node
*/

Pro: no need to change the config Con: can become tedious, not easy to see server-only tests at first glances

Approach 2: use 2 separate configs​

Define jest.config.server.ts like this for example:

const baseConfig = require("./jest.config");
module.exports = {
...baseConfig,
testEnvironment: "node",
testMatch: [
(testMatch: [
"**/__tests__/**/*.server.[jt]s?(x)",
"**/?(*.)+(spec|test).server.[tj]s?(x)"
]),
],
};

Only tests named foobar.test.server.js (or ts, etc.) will be matched.

Pro: clean, can handle files per folder or per name using a Regex. Cons: using 2 configs => using 2 commands, you need to name your server only test specifically, or to isolate them in a specific folder, more difficult to compute coverage, more configuration

Approach 3: use one config with the projects options​

With the projects options, you can use 2 different Jest config depending on the file name or location. So you can use one config for server tests and one for client tests.

Pro: based on a core feature of Jest, will compute coverage correctly Cons: you need to name your server specifically or to isolate them in a specific folder