--- title: 'Using Apollo GraphQL in an Nx Workspace' slug: 'using-apollo-graphql-in-an-nx-workspace' authors: ['Philip Fulcher'] cover_image: '/blog/images/2025-02-21/header.avif' tags: [nx] description: 'Learn how to create an Nx workspace that provides a GraphQL API via Apollo Server, a React frontend, and code generated by GraphQL Codegen to save time on development.' --- Because of the Nx's robust support for a diverse ecosystem of JavaScript development, it enables you to build your entire full-stack application in a single repo. This allows you to share code and interfaces between your frontend and backend and acts as a multiplier on your development velocity. [GraphQL](https://graphql.org/) is a query language for your API. Because of its typed schema definition, it’s a great candidate for defining the contract between your API and its consumers. By using smart tools to generate code based on that schema, you can save yourself a lot of time and enforce better cooperation between your frontend and backend. In this article, you will build a simple GraphQL API that tracks some information about Lego sets. You’ll create this API using Apollo Server, and it will be consumed by a React application. You’ll have this all inside of an Nx Workspace in a single repository. In this article, you’ll learn how to: - Create an Nx workspace for both frontend and backend applications - Create a GraphQL API using [Apollo Server](https://www.apollographql.com/docs/apollo-server) - Generate frontend code and backend resolver types based on your GraphQL schema using [GraphQL Codegen](https://the-guild.dev/graphql/codegen) - Create a [React](https://react.dev/) application to consume your GraphQL API {% callout type="note" title="Minimal configuration" %} When given the option to enable another tool, like linting or testing, we're going to decline. This keeps this article focussed on GraphQL instead of having lint and test configs in the example. As you progress, feel free to enable these additional options if you'd like, especially if you're adding to an existing workspace that has those tools enabled. {% /callout %} An example repo with all the work you’ll be doing here can be found in [our Nx Recipes repo](https://github.com/nrwl/nx-recipes/tree/main/apollo-graphql) ## Create a new workspace Start by creating an Nx workspace: ```shell npx create-nx-workspace@latest --preset=node-monorepo nx-apollo ``` When prompted, answer the prompts as follows: ```shell ❯ npx create-nx-workspace@latest --preset node nx-apollo NX Let's create a new workspace [https://nx.dev/getting-started/intro] ✔ Application name · api ✔ What framework should be used? · none ✔ Would you like to generate a Dockerfile? [https://docs.docker.com/] · No ✔ Which CI provider would you like to use? · skip ✔ Would you like remote caching to make your build faster? · skip ``` ## Create GraphQL schema and project We want to generate model types from our schema that can be used by other projects in our workspace, so we'll start by creating a new project: ```shell npx nx g @nx/js:library --directory=libs/models-graphql models-graphql ``` When prompted, answer the prompts as follows: ```shell ❯ npx nx g @nx/js:library --directory=libs/models-graphql models-graphql NX Generating @nx/js:library ✔ Which bundler would you like to use to build the library? Choose 'none' to skip build setup. · none ✔ Which linter would you like to use? · none ✔ Which unit test runner would you like to use? · none ``` {% callout type="note" title="Why do I need a separate project?" %} When you have your GraphQL schema and generated models in a separate project, other projects can depend on it. This ensures that all projects are using the same version of the schema and models. This exemplifies the "modulith" structure for monorepos. [Read more](/blog/virtuous-cycle-of-workspace-structure) {% /callout %} You need a GraphQL schema to create the API, so write a very simple one with a single query and a single mutation. Create a file named `schema.graphql` in the new `models-graphql` project: ```graphql {% fileName="libs/models-graphql/src/lib/schema.graphql" %} type Set { id: Int! name: String year: Int numParts: Int } type Query { allSets: [Set] } type Mutation { addSet(name: String, year: String, numParts: Int): Set } ``` To start generating our models from this schema, we'll use [GraphQl Codegen](https://the-guild.dev/graphql/codegen). Install the packages needed: ```shell npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/typescript-react-apollo ``` GraphQL Codegen is controlled by a configuration file named `codegen.ts` in each project that needs it. Create one for `models-graphql`: ```typescript {% fileName="libs/models-graphql/codegen.ts" %} import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'libs/models-graphql/src/lib/schema.graphql', generates: { 'libs/models-graphql/src/lib/__generated__/models.ts': { plugins: ['typescript'], config: { avoidOptionals: true, }, }, }, }; export default config; ``` To run GraphqlQL Codegen, we need a target added to our project. Add this to the `project.json` for `models-graphql`: ```json {% fileName="libs/models-graphql/project.json" %} "targets": { "codegen": { "command": "npx graphql-codegen --config {projectRoot}/codegen.ts" } } ``` Run the `codegen` task to generate our new models: ```shell npx nx codegen models-graphql ``` You should see the new models created in the `__generated__` directory in `models-graphql`. To use them outside the project, export them from the `index.ts`: ```typescript {% fileName="libs/models-graphql/src/index.ts" %} export * from './lib/__generated__/models'; ``` ## Create GraphQL API Use Apollo Server to create your GraphQL api. Start by installing the GraphQL modules needed for Apollo ```shell npm install @apollo/server graphql ``` The workspace was generated with a Node application for us, but we need to make some small changes to support ESM for Apollo Server. First, change these compiler options in `tsconfig.app.json`: ```json {% fileName="apps/api/tsconfig.app.json" %} "compilerOptions": { "lib": ["es2020"], "target": "es2020", "module": "esnext", "moduleResolution": "node", "esModuleInterop": true, ... } ``` And change the `build` target config in `project.json`: ```json {% fileName="apps/api/project.json" %} "targets": { "build": { ... "options": { ... "format": ["esm"] } } } ``` GraphQl Codegen has already created our models for our GraphQL schema, but it can also generate the resolver types we'll want to implement in Apollo Server. Like before, create a `codegen.ts` in the `api` application: ```typescript {% fileName="apps/api/codegen.ts" %} import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'libs/models-graphql/src/lib/schema.graphql', generates: { 'apps/api/src/__generated__/resolvers.ts': { plugins: ['add', 'typescript-resolvers'], config: { useIndexSignature: true, content: 'import * as types from "@nx-apollo/models-graphql"', namespacedImportName: 'types', }, }, }, }; export default config; ``` And add the task to our targets: ```json {% fileName="apps/api/project.json" %} "targets": { "codegen": { "command": "npx graphql-codegen --config {projectRoot}/codegen.ts" } } ``` And run the task: ```shell npx nx codegen api ``` And now there should be resolver types in the `__generated__` directory. We're ready to put create our Apollo Server application. Replace the contents of `main.ts` with this: ```typescript {% fileName="apps/api/src/main.ts" %} import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; import { Set } from '@nx-apollo/models-graphql'; import { readFileSync } from 'fs'; import { join } from 'path'; import { Resolvers } from './__generated__/resolvers'; // Note: this uses a path relative to the project's // root directory, which is the current working directory // if the server is executed using `npm run`. const typeDefs = readFileSync( join('libs/models-graphql/src/lib', 'schema.graphql'), { encoding: 'utf-8' } ); const sets: Set[] = [ { id: 1, name: 'Voltron', numParts: 2300, year: '2019', }, { id: 2, name: 'Ship in a Bottle', numParts: 900, year: '2019', }, ]; // Resolvers define how to fetch the types defined in your schema. // This resolver retrieves books from the "books" array above. const resolvers: Resolvers = { Query: { allSets: () => sets, }, Mutation: { addSet: (parent, args) => { const newSet = { id: sets.length + 1, name: args.name, year: args.year, numParts: +args.numParts, }; sets.push(newSet); return newSet; }, }, }; // The ApolloServer constructor requires two parameters: your schema // definition and your set of resolvers. const server = new ApolloServer({ typeDefs, resolvers, }); // Passing an ApolloServer instance to the `startStandaloneServer` function: // 1. creates an Express app // 2. installs your ApolloServer instance as middleware // 3. prepares your app to handle incoming requests const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`🚀 Server ready at: ${url}`); ``` This is already enough to see some progress when you run the `api` application. ```shell npx nx serve api ``` When the application is running, bring up the GraphQL Playground in your browser at [http://localhost:4000/](http://localhost:4000/) Here you can inspect your GraphQL schema as well as submit queries and mutations. This is a very simple resolver that holds data in memory. It returns the current contents of the sets array for the `allSets` query and allows users to add a new set using the `addSet` mutation. Add this resolver to the providers array in your app module: Go back to your GraphQL Playground and see if your queries return any data now. Try a query and a mutation: ```graphql query allSets { allSets { id name numParts } } mutation addSet { addSet(name: "My New Set", numParts: 200, year: "2020") { id } } ``` Now that the API is working, you’re ready to build a frontend to access this. ## Add React frontend Start by adding a React app to your workspace using the `@nx/react` plugin: ```shell npx nx add @nx/react ``` Create the React app using the generator: ```shell npx nx g @nx/react:app --directory=apps/frontend frontend ``` When prompted, answer the prompts as follows: ```shell ❯ npx nx g @nx/react:app --directory=apps/frontend frontend NX Generating @nx/react:application ✔ Which stylesheet format would you like to use? · tailwind ✔ Would you like to add React Router to this application? (y/N) · false ✔ Which bundler do you want to use to build the application? · vite ✔ Which linter would you like to use? · none ✔ What unit test runner should be used? · none ✔ Which E2E test runner would you like to use? · none ``` We use Tailwind styles here for convenience. It will allow us to add some simple styles to our app without adding CSS files and importing them. The Apollo client makes it easy to consume your GraphQL API. Install the client: ```shell npm install @apollo/client ``` Modify your `app.tsx` to provide the Apollo Client: ```typescript {% fileName="apps/frontend/src/main.tsx" %} import { StrictMode } from 'react'; import * as ReactDOM from 'react-dom/client'; import App from './app/app'; import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); const client = new ApolloClient({ uri: 'http://localhost:4000/graphql', cache: new InMemoryCache(), }); root.render( ); ``` ## Create React library Nx helps you break down your code into well-organized libraries for consumption by apps, so create a couple of React libraries to organize your work. Create a `data-access` library that handles communication with the backend and a `feature-sets` library that includes container components for displaying the Lego set data. In a real app, you might also create a `ui` library that includes reusable presentational components, but that is not part of this example. For more information on how to organize your React monorepo using Nx, read our book _Effective React Development with Nx_ by registering [here](https://go.nx.dev/react-book) To create the described project, run this command: ```shell npx nx g @nx/react:lib --directory=libs/feature-sets feature-sets ``` When prompted, answer the prompts as follows: ```shell npx nx g @nx/react:lib --directory=libs/feature-sets feature-sets NX Generating @nx/react:library ✔ Which bundler would you like to use to build the library? Choose 'none' to skip build setup. · none ✔ What unit test runner should be used? · none ``` ## Setup React Code Generation A tool called GraphQL Codegen makes the development of your feature library faster. You need to create some GraphQL queries and mutations for the frontend to consume. Create a file named `operations.graphql` in the projects: ```graphql {% fileName="libs/feature-sets/src/lib/operations.graphql" %} mutation addSet($name: String!, $year: String!, $numParts: Int!) { addSet(name: $name, year: $year, numParts: $numParts) { id name numParts year } } query setList { allSets { id name numParts year } } ``` Once again create a `codegen.ts` for the project: ```typescript {% fileName="libs/feature-sets/codegen.ts" %} import { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'libs/models-graphql/src/lib/schema.graphql', documents: ['libs/feature-sets/src/**/*.graphql'], generates: { 'libs/feature-sets/src/lib/__generated__/operations.ts': { plugins: ['add', 'typescript-operations', 'typescript-react-apollo'], config: { useIndexSignature: true, content: 'import * as types from "@nx-apollo/models-graphql"', namespacedImportName: 'types', }, }, }, ignoreNoDocuments: true, }; export default config; ``` This configuration grabs all of your GraphQL files and generates all the needed types and services to consume the API. Add a new task in `project.json` to run this code generator: ```json {% fileName="libs/feature-sets/project.json" %} "targets": { "codegen": { "command": "npx graphql-codegen --config {projectRoot}/codegen.ts" } } ``` Now you can run that task using the Nx CLI: ```shell npx nx codegen feature-sets ``` You should now have a folder called `__generated__` in your `feature-sets` library with a file named `operations.ts`. It contains typing information about the GraphQL schema and the operations you defined. It even has some hooks that make consuming this API superfast. ## Create React components You now have everything needed to start building your React components. Create two components: a list of Lego sets and a form to add a Lego set. Use Nx generators to scaffold these: ```shell npx nx generate @nx/react:component libs/feature-sets/src/lib/add-set-form ``` ```shell npx nx generate @nx/react:component libs/feature-sets/src/lib/set-list ``` When prompted, answer the prompts as follows: ```shell ❯ npx nx generate @nx/react:component libs/feature-sets/src/lib/add-set-form NX Generating @nx/react:component ✔ Should this component be exported in the project? (y/N) · false ``` In the `SetList` component, add the following: ```tsx {% fileName="libs/feature-sets/src/lib/set-list.tsx" %} import { useSetListQuery } from './__generated__/operations'; export function SetList() { const { loading, data } = useSetListQuery(); return loading ? (

Loading ...

) : ( ); } export default SetList; ``` Notice how `useSetListQuery` is imported from the data-access library. This is a hook generated by GraphQL Codegen that provides the results of the `SetList` query. This entire pipeline is type-safe, using the types generated by GraphQL Codegen. In the `AddSetForm` component, add the following: ```tsx {% fileName="libs/feature-sets/src/lib/add-set-form.tsx" %} import { useRef } from 'react'; import { useAddSetMutation } from './__generated__/operations'; export function AddSetForm() { const formRef = useRef(null); const [addSet] = useAddSetMutation({ refetchQueries: ['setList'], }); const handleSubmit = (formData: FormData) => { const name = formData.get('name')?.toString(); const year = formData.get('year')?.toString(); const numParts = parseInt(formData.get('numParts')?.toString() || '0', 10); if (name && year && numParts > 0) { addSet({ variables: { name, year, numParts } }); } formRef.current?.reset(); }; return (
); } export default AddSetForm; ``` Again, notice that the component imports hooks, queries, and typing information from our generated code to accomplish this. ## Integrate components into the app Final step: bring those new components into `FeatureSets` component: ```tsx {% fileName="libs/feature-sets/src/lib/feature-sets.tsx" %} import AddSetForm from './add-set-form'; import SetList from './set-list'; export function FeatureSets() { return (
); } export default FeatureSets; ``` And bring that component into your app: ```tsx {% fileName="apps/frontend/src/app/app.tsx" %} import { FeatureSets } from '@nx-apollo/feature-sets'; export function App() { return (

My Lego Sets

); } export default App; ``` If your API isn’t running already, go ahead and start it: ```shell npx nx serve api ``` And now start your React app in a separate terminal: ```shell npx nx serve frontend ``` Browse to [http://localhost:4200](http://localhost:4200) and see the results of your work! ## Extend `codegen` configuration The configuration for the `codegen` targets is a good start, but it's currently lacking two things: 1. [Caching](/features/cache-task-results) 2. [Dependent tasks](/concepts/task-pipeline-configuration) Without caching enabled, `codegen` tasks will be run every time, regardless if they need to be or not. And without dependent tasks configured, we can't be sure that `codegen` is run any time our generated code depends on the generated code in another project. IOn our example, the generated code in both `api` and `feature-sets` rely on the models generated in `models-graphql`. If we make changes to the schema in `models-graph` and then run `codegen` on `api`, our models will be out-of-sync and lead to errors. Let's fix both of these problems with a target default for `codegen`. In `nx.json`, add this: ```json {% fileName="nx.json" %} "targetDefaults": { ... "codegen": { "cache": true, "outputs": ["{projectRoot}/src/__generated__"], "inputs": ["{workspaceRoot}/libs/models-graphql/src/lib/schema.graphql","{projectRoot}/**/*.graphql"], "dependsOn": ["^codegen"] } } ``` Now try running `codegen` for `api` to see that `codegen` for `models-graphql` is run first: ```shell ❯ npx nx codegen api ✔ 1/1 dependent project tasks succeeded [0 read from cache] Hint: you can run the command with --verbose to see the full dependent project outputs ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— > nx run api:codegen > npx graphql-codegen --config apps/api/codegen.ts ✔ Parse Configuration ✔ Generate outputs ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— NX Successfully ran target codegen for project api and 1 task it depends on (2s) ``` Try running the command again, and you'll see that the results are pulled from the cache, and the task ends immediately. ## Further Reading - [Apollo Server](https://www.apollographql.com/docs/apollo-server) - [Apollo React Client](https://www.apollographql.com/docs/react/) - [GraphQL Codegen](https://the-guild.dev/graphql/codegen) - 🧠 [Nx Docs](/getting-started/intro) - 👩‍💻 [Nx GitHub](https://github.com/nrwl/nx) - 💬 [Nx Official Discord Server](https://go.nx.dev/community) - 📹 [Nx Youtube Channel](https://www.youtube.com/@nxdevtools)