Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): with-grafbase #42898

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/with-grafbase/.env.local.example
@@ -0,0 +1,2 @@
GRAFBASE_API_URL=http://localhost:4000/graphql
GRAFBASE_API_KEY=
38 changes: 38 additions & 0 deletions examples/with-grafbase/.gitignore
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

.grafbase
4 changes: 4 additions & 0 deletions examples/with-grafbase/.vscode/settings.json
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
68 changes: 68 additions & 0 deletions examples/with-grafbase/README.md
@@ -0,0 +1,68 @@
# Next.js with Grafbase

This example shows to use [Grafbase](https://grafbase.com) with Next.js. This example features fetching from a local GraphQL backend powered by the Grafbase CLI, and GraphQL Code Generator for making type-safe queries.

## Demo

You can see a demo of this online at [https://nextjs-with-grafbase.vercel.app](https://nextjs-with-grafbase.vercel.app).

## Deploy

First deploy this to Grafbase to get your backend API URL and Key:

[![Deploy to Grafbase](https://grafbase.com/button)](https://grafbase.com/new/configure?template=Next Example&source=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-grafbase)

Then deploy this example using [Vercel](https://vercel.com):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-grafbase&env=GRAFBASE_API_URL,GRAFBASE_API_KEY)

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:

```bash
npx create-next-app --example with-grafbase with-grafbase-app
```

```bash
yarn create next-app --example with-grafbase with-grafbase-app
```

```bash
pnpm create next-app --example with-grafbase with-grafbase-app
```

To run the example locally you need to:

1. Copy the `.env.local.example` to `.env.local` and provide your API URL and API Key: `cp .env.local.example .env.local` — the defaults will be fine for development mode.

2. Run the [Grafbase CLI](https://grafbase.com/cli) using `npx grafbase@latest dev`

3. Populate the backend with some `Post` entries using a GraphQL mutation:

```graphql
mutation {
postCreate(
input: {
title: "I love Next.js!"
slug: "i-love-nextjs"
comments: [{ create: { message: "me too!" } }]
}
) {
post {
id
slug
}
}
}
```

4. Run the app locally and go to [http://localhost:3000](http://localhost:3000) to navigate to each post page! This data is fetched from the local backend.

5. Optionally run `npm run codegen` to watch for any changes to queries inside of the app and automatically generate types.

## Learn more

- [Grafbase Quickstart](https://grafbase.com/docs/quickstart/get-started) — get started with Grafbase, quickly!
- [Create an account](https://grafbase.com/sign-up) — deploy to the edge with Grafbase!
- [Next.js Documentation](https://nextjs.org/docs) — learn more about Next.js
3 changes: 3 additions & 0 deletions examples/with-grafbase/app/globals.css
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
7 changes: 7 additions & 0 deletions examples/with-grafbase/app/head.tsx
@@ -0,0 +1,7 @@
const Head = () => (
<>
<title>Grafbase + Next.js</title>
</>
)

export default Head
80 changes: 80 additions & 0 deletions examples/with-grafbase/app/layout.tsx
@@ -0,0 +1,80 @@
import './globals.css'

import Link from 'next/link'

import { graphql } from '../gql'
import { grafbase } from '../lib/grafbase'

const GetAllPostsDocument = graphql(/* GraphQL */ `
query GetAllPosts($first: Int!) {
postCollection(first: $first) {
edges {
node {
id
title
slug
}
}
}
}
`)

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
const { postCollection } = await grafbase.request(GetAllPostsDocument, {
first: 50
})

return (
<html lang="en">
<head>
<title>Grafbase + Next.js 13</title>
</head>
<body>
<div className="flex">
<nav className="w-[350px] flex flex-col justify-between h-screen overflow-y-auto bg-gray-100">
<ul className="p-8 space-y-2">
<li className="mb-6">
<Link
href="/"
className="py-2 rounded-md shadow-sm block px-3 text-gray-600 hover:text-gray-800 transition bg-white"
>
Home
</Link>
</li>
<li className="px-3 py-2 uppercase text-xs text-gray-800 font-semibold">
Posts
</li>
{postCollection?.edges?.map((edge) =>
edge?.node ? (
<li key={edge.node.id}>
<Link
href={`/posts/${edge.node.slug}`}
className="py-2 rounded-md shadow-sm block px-3 text-gray-600 hover:text-gray-800 transition bg-white"
>
{edge.node.title}
</Link>
</li>
) : null
)}
<li>
<Link
href="/posts/not-found"
className="py-2 rounded-md shadow-sm block px-3 text-gray-600 hover:text-gray-800 transition bg-white"
>
Show 404 page
</Link>
</li>
</ul>
</nav>
<main className="flex-1 p-6 md:p-24">
<div className="max-w-3xl mx-auto">
<div className="prose max-w-none">{children}</div>
</div>
</main>
</div>
</body>
</html>
)
}

export default RootLayout
13 changes: 13 additions & 0 deletions examples/with-grafbase/app/page.tsx
@@ -0,0 +1,13 @@
const Page = async () => {
return (
<>
<h1>Next.js 13 + Grafbase</h1>
<p>
Once you've added some posts using the GraphQL Playground, you can
explore each post by clicking the link in the nav.
</p>
</>
)
}

export default Page
34 changes: 34 additions & 0 deletions examples/with-grafbase/app/posts/[slug]/page.tsx
@@ -0,0 +1,34 @@
import { graphql } from '../../../gql'
import { grafbase } from '../../../lib/grafbase'

export const revalidate = 3600

const GetPostBySlugDocument = graphql(/* GraphQL */ `
query GetPostBySlug($slug: String!) {
post(by: { slug: $slug }) {
id
title
slug
}
}
`)

const Page = async ({ params }: { params: { slug: string } }) => {
const { post } = await grafbase.request(GetPostBySlugDocument, {
slug: params.slug
})

if (!post) {
// optionally import notFound from next/navigation
return <h1>404: Not Found</h1>
}

return (
<>
<h1>{post.title}</h1>
<pre>{JSON.stringify(post, null, 2)}</pre>
</>
)
}

export default Page
26 changes: 26 additions & 0 deletions examples/with-grafbase/codegen.ts
@@ -0,0 +1,26 @@
import { CodegenConfig } from '@graphql-codegen/cli'

const url = process.env.GRAFBASE_API_URL as string
const xApiKey = process.env.GRAFBASE_API_KEY as string

const config: CodegenConfig = {
schema: [
{
[url]: {
headers: {
'x-api-key': xApiKey
}
}
}
],
documents: ['app/**/*.tsx', 'app/**/*.ts'],
ignoreNoDocuments: true,
generates: {
'./gql/': {
preset: 'client',
plugins: []
}
}
}

export default config
40 changes: 40 additions & 0 deletions examples/with-grafbase/gql/fragment-masking.ts
@@ -0,0 +1,40 @@
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';


export type FragmentType<TDocumentType extends DocumentNode<any, any>> = TDocumentType extends DocumentNode<
infer TType,
any
>
? TType extends { ' $fragmentName'?: infer TKey }
? TKey extends string
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
: never
: never
: never;

// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: FragmentType<DocumentNode<TType, any>>
): TType;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: FragmentType<DocumentNode<TType, any>> | null | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>>
): ReadonlyArray<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>> | null | undefined
): ReadonlyArray<TType> | null | undefined
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: FragmentType<DocumentNode<TType, any>> | ReadonlyArray<FragmentType<DocumentNode<TType, any>>> | null | undefined
): TType | ReadonlyArray<TType> | null | undefined {
return fragmentType as any
}
18 changes: 18 additions & 0 deletions examples/with-grafbase/gql/gql.ts
@@ -0,0 +1,18 @@
/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

const documents = {
"\n query GetAllPosts($first: Int!) {\n postCollection(first: $first) {\n edges {\n node {\n id\n title\n slug\n }\n }\n }\n }\n": types.GetAllPostsDocument,
"\n query GetPostBySlug($slug: String!) {\n post(by: { slug: $slug }) {\n id\n title\n slug\n }\n }\n": types.GetPostBySlugDocument,
};

export function graphql(source: "\n query GetAllPosts($first: Int!) {\n postCollection(first: $first) {\n edges {\n node {\n id\n title\n slug\n }\n }\n }\n }\n"): (typeof documents)["\n query GetAllPosts($first: Int!) {\n postCollection(first: $first) {\n edges {\n node {\n id\n title\n slug\n }\n }\n }\n }\n"];
export function graphql(source: "\n query GetPostBySlug($slug: String!) {\n post(by: { slug: $slug }) {\n id\n title\n slug\n }\n }\n"): (typeof documents)["\n query GetPostBySlug($slug: String!) {\n post(by: { slug: $slug }) {\n id\n title\n slug\n }\n }\n"];

export function graphql(source: string): unknown;
export function graphql(source: string) {
return (documents as any)[source] ?? {};
}

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;