├── .gitignore ├── public ├── favicon.ico └── vercel.svg ├── pages ├── _app.js ├── api │ └── hello.js └── index.js ├── Pipfile ├── codegen.yml ├── next.config.js ├── styles ├── globals.css └── Home.module.css ├── requirements.txt ├── test.py ├── package.json ├── graphql ├── operations.graphql └── graphql.ts ├── README.md ├── LICENSE ├── models.py └── app.py /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | backup 4 | database.db 5 | .next -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasoob/sqlalchemy-strawberry-fastapi-nextjs/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return 5 | } 6 | 7 | export default MyApp 8 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | 10 | [requires] 11 | python_version = "3.10" 12 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "http://127.0.0.1:8000/graphql" 3 | documents: './graphql/**/*.graphql' 4 | generates: 5 | graphql/graphql.ts: 6 | plugins: 7 | - "typescript" 8 | - "typescript-operations" 9 | - "typescript-urql" -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | async rewrites() { 5 | return { 6 | beforeFiles: [ 7 | { 8 | source: "/graphql", 9 | destination: "http://localhost:8000/graphql", 10 | }, 11 | ], 12 | }; 13 | }, 14 | }; -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiosqlite==0.17.0 2 | anyio==3.5.0 3 | asgiref==3.5.0 4 | backports.cached-property==1.0.1 5 | click==8.1.3 6 | fastapi==0.75.2 7 | graphql-core==3.2.1 8 | greenlet==1.1.2 9 | h11==0.13.0 10 | httptools==0.4.0 11 | idna==3.3 12 | pydantic==1.9.0 13 | Pygments==2.12.0 14 | python-dateutil==2.8.2 15 | python-dotenv==0.20.0 16 | python-multipart==0.0.5 17 | PyYAML==6.0 18 | six==1.16.0 19 | sniffio==1.2.0 20 | SQLAlchemy==1.4.36 21 | starlette==0.17.1 22 | strawberry-graphql==0.109.1 23 | typing-extensions==4.2.0 24 | uvicorn==0.17.6 25 | uvloop==0.16.0 26 | watchgod==0.8.2 27 | websockets==10.3 28 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import strawberry 2 | 3 | from fastapi import FastAPI 4 | from strawberry.fastapi import GraphQLRouter 5 | 6 | authors: list[str] = [] 7 | 8 | @strawberry.type 9 | class Query: 10 | @strawberry.field 11 | def all_authors(self, info) -> list[str]: 12 | return authors 13 | 14 | @strawberry.type 15 | class Mutation: 16 | @strawberry.field 17 | def add_author(name: str) -> str: 18 | authors.append(name) 19 | return name 20 | 21 | schema = strawberry.Schema(query=Query, mutation=Mutation) 22 | 23 | graphql_app = GraphQLRouter(schema) 24 | 25 | app = FastAPI() 26 | app.include_router(graphql_app, prefix="/graphql") -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next_strawberry", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "codegen": "graphql-codegen --config codegen.yml" 11 | }, 12 | "dependencies": { 13 | "@graphql-codegen/cli": "^2.6.2", 14 | "graphql": "^16.4.0", 15 | "next": "12.1.6", 16 | "react": "18.1.0", 17 | "react-dom": "18.1.0" 18 | }, 19 | "devDependencies": { 20 | "@graphql-codegen/cli": "2.6.2", 21 | "@graphql-codegen/typescript": "2.4.10", 22 | "@graphql-codegen/typescript-operations": "2.3.7", 23 | "@graphql-codegen/typescript-urql": "^3.5.9", 24 | "eslint": "8.14.0", 25 | "eslint-config-next": "12.1.6", 26 | "urql": "^2.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /graphql/operations.graphql: -------------------------------------------------------------------------------- 1 | query Books { 2 | books { 3 | ...BookFields 4 | } 5 | } 6 | 7 | query Authors { 8 | authors { 9 | ...AuthorFields 10 | } 11 | } 12 | 13 | fragment BookFields on Book { 14 | id 15 | name 16 | author { 17 | name 18 | } 19 | } 20 | 21 | fragment AuthorFields on Author { 22 | id 23 | name 24 | } 25 | 26 | mutation AddBook($name: String!, $authorName: String!) { 27 | addBook(name: $name, authorName: $authorName) { 28 | __typename 29 | ... on Book { 30 | __typename 31 | ...BookFields 32 | } 33 | } 34 | } 35 | 36 | mutation AddAuthor($name: String!) { 37 | addAuthor(name: $name) { 38 | __typename 39 | ... on AuthorExists { 40 | __typename 41 | message 42 | } 43 | ... on Author { 44 | __typename 45 | ...AuthorFields 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). It demonstrates how to use Strawberry, FastAPI, SQLAlchemy, and NextJS together. It makes use of `graphql-codegen` to automatically generate `urql` hooks based on GraphQL API that are ready to use in your React/NextJS code. 2 | 3 | 4 | ## Getting Started 5 | 6 | First, install the Python dependencies: 7 | 8 | ``` 9 | $ pip install -r requirements.txt 10 | ``` 11 | 12 | Next, install the npm based dependencies: 13 | 14 | ``` 15 | $ npm install 16 | ``` 17 | 18 | Create the db: 19 | 20 | ``` 21 | $ python models.py 22 | ``` 23 | 24 | Now, run the `uvicorn` server: 25 | 26 | ``` 27 | $ uvicorn app:app --reload --host '::' 28 | ``` 29 | 30 | Finally, run the NextJS development server: 31 | 32 | ```bash 33 | npm run dev 34 | # or 35 | yarn dev 36 | ``` 37 | 38 | Now you can go to `http://127.0.0.1:3000/graphql` to explore the interactive GraphiQL app and start developing your NextJS app. -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2022 M.Yasoob Ullah Khalid ☺ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from contextlib import asynccontextmanager 3 | from typing import AsyncGenerator, Optional 4 | 5 | from sqlalchemy import Column, ForeignKey, Integer, String 6 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine 7 | from sqlalchemy.ext.declarative import declarative_base 8 | from sqlalchemy.orm import relationship, sessionmaker 9 | 10 | Base = declarative_base() 11 | 12 | 13 | class Author(Base): 14 | __tablename__ = "authors" 15 | id: int = Column(Integer, primary_key=True, index=True) 16 | name: str = Column(String, nullable=False, unique=True) 17 | 18 | books: list["Book"] = relationship("Book", lazy="joined", back_populates="author") 19 | 20 | 21 | class Book(Base): 22 | __tablename__ = "books" 23 | id: int = Column(Integer, primary_key=True, index=True) 24 | name: str = Column(String, nullable=False) 25 | author_id: Optional[int] = Column(Integer, ForeignKey(Author.id), nullable=True) 26 | 27 | author: Optional[Author] = relationship(Author, lazy="joined", back_populates="books") 28 | 29 | 30 | engine = create_async_engine( 31 | "sqlite+aiosqlite:///./database.db", connect_args={"check_same_thread": False} 32 | ) 33 | 34 | async_session = sessionmaker( 35 | bind=engine, 36 | class_=AsyncSession, 37 | expire_on_commit=False, 38 | autocommit=False, 39 | autoflush=False, 40 | ) 41 | 42 | 43 | @asynccontextmanager 44 | async def get_session() -> AsyncGenerator[AsyncSession, None]: 45 | async with async_session() as session: 46 | async with session.begin(): 47 | try: 48 | yield session 49 | finally: 50 | await session.close() 51 | 52 | 53 | async def _async_main(): 54 | async with engine.begin() as conn: 55 | await conn.run_sync(Base.metadata.drop_all) 56 | await conn.run_sync(Base.metadata.create_all) 57 | await engine.dispose() 58 | 59 | 60 | if __name__ == "__main__": 61 | print("Dropping and creating tables") 62 | asyncio.run(_async_main()) 63 | print("Done.") -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import styles from '../styles/Home.module.css' 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | 9 | Create Next App 10 | 11 | 12 | 13 | 14 |
15 |

16 | Welcome to Next.js! 17 |

18 | 19 |

20 | Get started by editing{' '} 21 | pages/index.js 22 |

23 | 24 |
25 | 26 |

Documentation →

27 |

Find in-depth information about Next.js features and API.

28 |
29 | 30 | 31 |

Learn →

32 |

Learn about Next.js in an interactive course with quizzes!

33 |
34 | 35 | 39 |

Examples →

40 |

Discover and deploy boilerplate example Next.js projects.

41 |
42 | 43 | 47 |

Deploy →

48 |

49 | Instantly deploy your Next.js site to a public URL with Vercel. 50 |

51 |
52 |
53 |
54 | 55 | 67 |
68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import strawberry 2 | from typing import Optional 3 | 4 | from fastapi import FastAPI 5 | from sqlalchemy import select 6 | from strawberry.fastapi import GraphQLRouter 7 | from strawberry.types import Info 8 | from strawberry.dataloader import DataLoader 9 | 10 | import models 11 | 12 | @strawberry.type 13 | class Author: 14 | id: strawberry.ID 15 | name: str 16 | 17 | @strawberry.field 18 | async def books(self, info: Info) -> list["Book"]: 19 | books = await info.context["books_by_author"].load(self.id) 20 | return [Book.marshal(book) for book in books] 21 | 22 | @classmethod 23 | def marshal(cls, model: models.Author) -> "Author": 24 | return cls(id=strawberry.ID(str(model.id)), name=model.name) 25 | 26 | 27 | @strawberry.type 28 | class Book: 29 | id: strawberry.ID 30 | name: str 31 | author: Optional[Author] = None 32 | 33 | @classmethod 34 | def marshal(cls, model: models.Book) -> "Book": 35 | return cls( 36 | id=strawberry.ID(str(model.id)), 37 | name=model.name, 38 | author=Author.marshal(model.author) if model.author else None, 39 | ) 40 | 41 | @strawberry.type 42 | class AuthorExists: 43 | message: str = "Author with this name already exist" 44 | 45 | @strawberry.type 46 | class AuthorNotFound: 47 | message: str = "Couldn't find an author with the supplied name" 48 | 49 | @strawberry.type 50 | class AuthorNameMissing: 51 | message: str = "Please supply an author name" 52 | 53 | AddBookResponse = strawberry.union("AddBookResponse", (Book, AuthorNotFound, AuthorNameMissing)) 54 | AddAuthorResponse = strawberry.union("AddAuthorResponse", (Author, AuthorExists)) 55 | 56 | 57 | all_tasks: list = [] 58 | 59 | @strawberry.type 60 | class Query: 61 | @strawberry.field 62 | async def books(self) -> list[Book]: 63 | async with models.get_session() as s: 64 | sql = select(models.Book).order_by(models.Book.name) 65 | db_book = (await s.execute(sql)).scalars().unique().all() 66 | return [Book.marshal(book) for book in db_book] 67 | 68 | @strawberry.field 69 | async def authors(self) -> list[Author]: 70 | async with models.get_session() as s: 71 | sql = select(models.Author).order_by(models.Author.name) 72 | db_authors = (await s.execute(sql)).scalars().unique().all() 73 | return [Author.marshal(loc) for loc in db_authors] 74 | 75 | 76 | @strawberry.type 77 | class Mutation: 78 | @strawberry.mutation 79 | async def add_book(self, name: str, author_name: Optional[str]) -> AddBookResponse: 80 | async with models.get_session() as s: 81 | db_author = None 82 | if author_name: 83 | sql = select(models.Author).where(models.Author.name == author_name) 84 | db_author = (await s.execute(sql)).scalars().first() 85 | if not db_author: 86 | return AuthorNotFound() 87 | else: 88 | return AuthorNameMissing() 89 | db_book = models.Book(name=name, author=db_author) 90 | s.add(db_book) 91 | await s.commit() 92 | return Book.marshal(db_book) 93 | 94 | @strawberry.mutation 95 | async def add_author(self, name: str) -> AddAuthorResponse: 96 | async with models.get_session() as s: 97 | sql = select(models.Author).where(models.Author.name == name) 98 | existing_db_author = (await s.execute(sql)).first() 99 | if existing_db_author is not None: 100 | return AuthorExists() 101 | db_author = models.Author(name=name) 102 | s.add(db_author) 103 | await s.commit() 104 | return Author.marshal(db_author) 105 | 106 | 107 | async def load_books_by_author(keys: list) -> list[Book]: 108 | async with models.get_session() as s: 109 | all_queries = [select(models.Book).where(models.Book.author_id == key) for key in keys] 110 | data = [(await s.execute(sql)).scalars().unique().all() for sql in all_queries] 111 | print(keys, data) 112 | return data 113 | 114 | 115 | async def load_author_by_book(keys: list) -> list[Book]: 116 | async with models.get_session() as s: 117 | sql = select(models.Author).where(models.Author.id in keys) 118 | data = (await s.execute(sql)).scalars().unique().all() 119 | if not data: 120 | data.append([]) 121 | return data 122 | 123 | 124 | async def get_context() -> dict: 125 | return { 126 | "author_by_book": DataLoader(load_fn=load_author_by_book), 127 | "books_by_author": DataLoader(load_fn=load_books_by_author), 128 | } 129 | 130 | schema = strawberry.Schema(query=Query, mutation=Mutation) 131 | graphql_app = GraphQLRouter(schema, context_getter=get_context) 132 | 133 | app = FastAPI() 134 | app.include_router(graphql_app, prefix="/graphql") -------------------------------------------------------------------------------- /graphql/graphql.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import * as Urql from 'urql'; 3 | export type Maybe = T | null; 4 | export type InputMaybe = Maybe; 5 | export type Exact = { [K in keyof T]: T[K] }; 6 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; 7 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; 8 | export type Omit = Pick>; 9 | /** All built-in and custom scalars, mapped to their actual values */ 10 | export type Scalars = { 11 | ID: string; 12 | String: string; 13 | Boolean: boolean; 14 | Int: number; 15 | Float: number; 16 | }; 17 | 18 | export type AddAuthorResponse = Author | AuthorExists; 19 | 20 | export type AddBookResponse = AuthorNameMissing | AuthorNotFound | Book; 21 | 22 | export type Author = { 23 | __typename?: 'Author'; 24 | books: Array; 25 | id: Scalars['ID']; 26 | name: Scalars['String']; 27 | }; 28 | 29 | export type AuthorExists = { 30 | __typename?: 'AuthorExists'; 31 | message: Scalars['String']; 32 | }; 33 | 34 | export type AuthorNameMissing = { 35 | __typename?: 'AuthorNameMissing'; 36 | message: Scalars['String']; 37 | }; 38 | 39 | export type AuthorNotFound = { 40 | __typename?: 'AuthorNotFound'; 41 | message: Scalars['String']; 42 | }; 43 | 44 | export type Book = { 45 | __typename?: 'Book'; 46 | author?: Maybe; 47 | id: Scalars['ID']; 48 | name: Scalars['String']; 49 | }; 50 | 51 | export type Mutation = { 52 | __typename?: 'Mutation'; 53 | addAuthor: AddAuthorResponse; 54 | addBook: AddBookResponse; 55 | }; 56 | 57 | 58 | export type MutationAddAuthorArgs = { 59 | name: Scalars['String']; 60 | }; 61 | 62 | 63 | export type MutationAddBookArgs = { 64 | authorName?: InputMaybe; 65 | name: Scalars['String']; 66 | }; 67 | 68 | export type Query = { 69 | __typename?: 'Query'; 70 | authors: Array; 71 | books: Array; 72 | }; 73 | 74 | export type BooksQueryVariables = Exact<{ [key: string]: never; }>; 75 | 76 | 77 | export type BooksQuery = { __typename?: 'Query', books: Array<{ __typename?: 'Book', id: string, name: string, author?: { __typename?: 'Author', name: string } | null }> }; 78 | 79 | export type AuthorsQueryVariables = Exact<{ [key: string]: never; }>; 80 | 81 | 82 | export type AuthorsQuery = { __typename?: 'Query', authors: Array<{ __typename?: 'Author', id: string, name: string }> }; 83 | 84 | export type BookFieldsFragment = { __typename?: 'Book', id: string, name: string, author?: { __typename?: 'Author', name: string } | null }; 85 | 86 | export type AuthorFieldsFragment = { __typename?: 'Author', id: string, name: string }; 87 | 88 | export type AddBookMutationVariables = Exact<{ 89 | name: Scalars['String']; 90 | authorName: Scalars['String']; 91 | }>; 92 | 93 | 94 | export type AddBookMutation = { __typename?: 'Mutation', addBook: { __typename: 'AuthorNameMissing' } | { __typename: 'AuthorNotFound' } | { __typename: 'Book', id: string, name: string, author?: { __typename?: 'Author', name: string } | null } }; 95 | 96 | export type AddAuthorMutationVariables = Exact<{ 97 | name: Scalars['String']; 98 | }>; 99 | 100 | 101 | export type AddAuthorMutation = { __typename?: 'Mutation', addAuthor: { __typename: 'Author', id: string, name: string } | { __typename: 'AuthorExists', message: string } }; 102 | 103 | export const BookFieldsFragmentDoc = gql` 104 | fragment BookFields on Book { 105 | id 106 | name 107 | author { 108 | name 109 | } 110 | } 111 | `; 112 | export const AuthorFieldsFragmentDoc = gql` 113 | fragment AuthorFields on Author { 114 | id 115 | name 116 | } 117 | `; 118 | export const BooksDocument = gql` 119 | query Books { 120 | books { 121 | ...BookFields 122 | } 123 | } 124 | ${BookFieldsFragmentDoc}`; 125 | 126 | export function useBooksQuery(options?: Omit, 'query'>) { 127 | return Urql.useQuery({ query: BooksDocument, ...options }); 128 | }; 129 | export const AuthorsDocument = gql` 130 | query Authors { 131 | authors { 132 | ...AuthorFields 133 | } 134 | } 135 | ${AuthorFieldsFragmentDoc}`; 136 | 137 | export function useAuthorsQuery(options?: Omit, 'query'>) { 138 | return Urql.useQuery({ query: AuthorsDocument, ...options }); 139 | }; 140 | export const AddBookDocument = gql` 141 | mutation AddBook($name: String!, $authorName: String!) { 142 | addBook(name: $name, authorName: $authorName) { 143 | __typename 144 | ... on Book { 145 | __typename 146 | ...BookFields 147 | } 148 | } 149 | } 150 | ${BookFieldsFragmentDoc}`; 151 | 152 | export function useAddBookMutation() { 153 | return Urql.useMutation(AddBookDocument); 154 | }; 155 | export const AddAuthorDocument = gql` 156 | mutation AddAuthor($name: String!) { 157 | addAuthor(name: $name) { 158 | __typename 159 | ... on AuthorExists { 160 | __typename 161 | message 162 | } 163 | ... on Author { 164 | __typename 165 | ...AuthorFields 166 | } 167 | } 168 | } 169 | ${AuthorFieldsFragmentDoc}`; 170 | 171 | export function useAddAuthorMutation() { 172 | return Urql.useMutation(AddAuthorDocument); 173 | }; --------------------------------------------------------------------------------