Build a Next App with a full GraphQL API from just a JSON or CSV file

Introduction

In this guide, we'll be using a JSON or CSV file with some data in it to build a fully featured GraphQL API inside a NextJS application.

For this to work, we'll need some tools, here are links to all the tools used in this guide if you'd like to check any of them out or potentially explore replacements:

Create a MongoDB database and add your data (skip if you already have a MongoDB database with data in it)

Head to mongodb.com and sign up for a free Atlas account or set up mongodb locally

Create your project

image

Create a cluster, select SHARED for the free tier.

image

Create your user and add your IP address to the list of allowed IP addresses (or 0.0.0.0 to allow any connection).

image

Press connect on your cluster on mongodb.com

image

Click on Compass

image

Download Compass if you don't have it, and copy the connection string

image

Paste the connection string into Compass, replacing with the password for the database user you created earlier and press connect

Click databases

image

Click create

image

In the database name and collection name fields, describe the data type your database will contain, for example a database of football players could be called football_players or players

image

Once the database is created, click on it

image

Click the collection

image

You can now upload your JSON or CSV file to the database to fill it

image

We now have a database with the data we need, you can close Compass.

Create a NextJS app with TypeScript

Answer the questions as you like but when asked

'if you want to use the new /app directory' no

'if you want to use src directory' no

yarn create next-app --typescript

Install dependencies

yarn add prisma typegraphql-prisma -D

yarn add @apollo/server @as-integrations/next @prisma/client graphql class-validator type-graphql@next reflect-metadata graphql-scalars graphql-fields @types/graphql-fields tslib

Initialize prisma

yarn prisma init --datasource-provider mongodb

Add mongodb connection and the eventual graphql api url to .env

DATABASE_URL="mongodb+srv://username:password@address.mongodb.net/tablename?retryWrites=true&w=majority"
NEXT_PUBLIC_API_URL="http://localhost:3000/api/graphql"

Pull the database schema into Prisma

yarn prisma db pull

Add generator to prisma

generator typegraphql {
  provider = "typegraphql-prisma"
  output   = "../prisma/generated/type-graphql"
  simpleResolvers = true
}

Create tsconfig.json or modify it at the NextJS root

{
  "compilerOptions": {
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    },
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext", "esnext.asynciterable", "dom", "dom.iterable"],
    "target": "es2018",
    "module": "commonjs",
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "skipLibCheck": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Generate the prisma schema

yarn prisma generate

Create /pages/api/graphql.ts

import 'reflect-metadata';
import { resolvers } from 'prisma/generated/type-graphql';
import { PrismaClient } from '@prisma/client';
import { ApolloServer } from '@apollo/server';
import * as tq from 'type-graphql';
import { startServerAndCreateNextHandler } from '@as-integrations/next';

const prisma = new PrismaClient();

const schema = tq.buildSchemaSync({
  resolvers,
  validate: false,
});

const server = new ApolloServer({ schema });

export default startServerAndCreateNextHandler(server, {
  context: async () => ({ prisma }),
});

Add this line to the top of _app.tsx

import 'reflect-metadata';

Start your Next app

yarn dev

Access the server at http://localhost:3000/api/graphql

Enabling CORS to use outside Vercel

Add the following to next.config.js.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Credentials', value: 'true' },
          { key: 'Access-Control-Allow-Origin', value: '*' },
          {
            key: 'Access-Control-Allow-Methods',
            value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT',
          },
          {
            key: 'Access-Control-Allow-Headers',
            value:
              'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

Automatically generate types for the client

Install graphql-code-generator

yarn add -D ts-node @graphql-codegen/cli @graphql-codegen/client-preset

Create codegen.ts

import { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: "http://localhost:3000/api/graphql",
  documents: ['pages/**/*.tsx'],
  ignoreNoDocuments: true, // for better experience with the watcher
  generates: {
    './gql/': {
      preset: 'client',
      plugins: [],
    },
  },
};

export default config;

Install concurrently

yarn add -D concurrently

Modify your dev script in package.json

"dev": "concurrently \"yarn next dev\" \"yarn graphql-codegen --watch\"",

Communicating with the API within Next

Add graphql-request and react-query

yarn add graphql-request react-query

Add React query provider and a global graphql-request client to _app.tsx

import 'reflect-metadata';
import type { AppProps } from 'next/app';
import { QueryClient, QueryClientProvider } from 'react-query';
import { GraphQLClient } from 'graphql-request';

const queryClient = new QueryClient();

export const client = new GraphQLClient(process.env.NEXT_PUBLIC_API_URL as string, {
  headers: {},
});

export default function App({ Component, pageProps }: AppProps) {
  return (
    <QueryClientProvider client={queryClient}>
      <Component {...pageProps} />
    </QueryClientProvider>
  );
}

You're now ready to use react-query with your types, here's an example index.tsx with a database of players

import Head from 'next/head';
import { graphql } from 'gql';
import { useQuery } from 'react-query';
import React from 'react';
import { client } from './_app';

const findManyPlayers = graphql(`
  query FindManyPlayers($take: Int) {
    findManyPlayers(take: $take) {
      age
      hits
      name
      id
      nationality
      overall
      player_id
      position
      potential
      team
    }
  }
`);

export default function Home() {
  const { data } = useQuery(['players'], async () =>
    client.request(findManyPlayers, {
      take: 5,
    })
  );
  if (!data) return <></>;
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name='description' content='Generated by create next app' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        <div>
          {data.findManyPlayers.map((player) => (
            <React.Fragment key={player.id}>
              <p>{player.name}</p>
            </React.Fragment>
          ))}
        </div>
      </main>
    </>
  );
}