DEV Community

Ali Raslan
Ali Raslan

Posted on

Build a Next App with a full GraphQL API using 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's 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
image

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Initialize prisma

yarn prisma init --datasource-provider mongodb
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Pull the database schema into Prisma

yarn prisma db pull
Enter fullscreen mode Exit fullscreen mode

Add generator to prisma

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

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"]
}


Enter fullscreen mode Exit fullscreen mode

Generate the prisma schema

yarn prisma generate
Enter fullscreen mode Exit fullscreen mode

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 }),
});


Enter fullscreen mode Exit fullscreen mode

Add this line to the top of _app.tsx

import 'reflect-metadata';
Enter fullscreen mode Exit fullscreen mode

Start your Next app

yarn dev
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Automatically generate types for the client

Install graphql-code-generator

yarn add -D ts-node @graphql-codegen/cli @graphql-codegen/client-preset
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Install concurrently

yarn add -D concurrently
Enter fullscreen mode Exit fullscreen mode

Modify your dev script in package.json

"dev": "concurrently \"yarn next dev\" \"yarn graphql-codegen --watch\"",
Enter fullscreen mode Exit fullscreen mode

Communicating with the API within Next

Add graphql-request and react-query

yarn add graphql-request react-query
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)