Skip to content

Commit

Permalink
feat(examples): with-grafbase (#42898)
Browse files Browse the repository at this point in the history
  • Loading branch information
notrab committed Nov 15, 2022
1 parent 3fc31f4 commit fbf6bfa
Show file tree
Hide file tree
Showing 21 changed files with 891 additions and 0 deletions.
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://grafbase-with-nextjs-rsc.grafbase-vercel.dev](https://grafbase-with-nextjs-rsc.grafbase-vercel.dev).

## 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=NextExample&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
44 changes: 44 additions & 0 deletions examples/with-grafbase/gql/fragment-masking.ts
@@ -0,0 +1,44 @@
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
}
25 changes: 25 additions & 0 deletions examples/with-grafbase/gql/gql.ts
@@ -0,0 +1,25 @@
/* 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

0 comments on commit fbf6bfa

Please sign in to comment.