장재혁

Link front and back

Showing 39 changed files with 484 additions and 567 deletions
1 -import { ValidationPipe } from '@nestjs/common'; 1 +import { ValidationPipe } from '@nestjs/common'
2 -import { NestFactory } from '@nestjs/core'; 2 +import { NestFactory } from '@nestjs/core'
3 -import { AppModule } from './app.module'; 3 +import { AppModule } from './app.module'
4 4
5 async function bootstrap() { 5 async function bootstrap() {
6 - const app = await NestFactory.create(AppModule); 6 + const app = await NestFactory.create(AppModule)
7 7
8 - app.useGlobalPipes(new ValidationPipe()); 8 + app.useGlobalPipes(new ValidationPipe())
9 - await app.listen(3000); 9 + await app.listen(5000)
10 } 10 }
11 -bootstrap(); 11 +bootstrap()
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
10 "sourceMap": true, 10 "sourceMap": true,
11 "outDir": "./dist", 11 "outDir": "./dist",
12 "baseUrl": "./", 12 "baseUrl": "./",
13 - "incremental": true 13 + "incremental": true,
14 + "skipLibCheck": true
14 } 15 }
15 } 16 }
......
...@@ -2,9 +2,9 @@ overwrite: true ...@@ -2,9 +2,9 @@ overwrite: true
2 require: 2 require:
3 - ts-node/register 3 - ts-node/register
4 generates: 4 generates:
5 - ./types/api-generated-types.ts: 5 + ./src/types/api-generated-types.ts:
6 schema: "src/graphql/api/api.graphql" 6 schema: "src/graphql/api/api.graphql"
7 - # documents: "src/graphql/api/**/*.graphql" 7 + # documents: "src/graphql/api/*.graphql"
8 plugins: 8 plugins:
9 - "typescript" 9 - "typescript"
10 # - 'fragment-matcher' 10 # - 'fragment-matcher'
......
1 +"use strict";
2 +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 + if (k2 === undefined) k2 = k;
4 + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5 +}) : (function(o, m, k, k2) {
6 + if (k2 === undefined) k2 = k;
7 + o[k2] = m[k];
8 +}));
9 +var __exportStar = (this && this.__exportStar) || function(m, exports) {
10 + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11 +};
12 +Object.defineProperty(exports, "__esModule", { value: true });
13 +__exportStar(require("./types/api-generated-types"), exports);
1 +export declare type Maybe<T> = T | null;
2 +export declare type Exact<T extends {
3 + [key: string]: unknown;
4 +}> = {
5 + [K in keyof T]: T[K];
6 +};
7 +export declare type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
8 + [SubKey in K]?: Maybe<T[SubKey]>;
9 +};
10 +export declare type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
11 + [SubKey in K]: Maybe<T[SubKey]>;
12 +};
13 +/** All built-in and custom scalars, mapped to their actual values */
14 +export declare type Scalars = {
15 + ID: string;
16 + String: string;
17 + Boolean: boolean;
18 + Int: number;
19 + Float: number;
20 +};
21 +export declare type Comment = {
22 + __typename?: 'Comment';
23 + author: Scalars['String'];
24 + content: Scalars['String'];
25 + created_date: Scalars['String'];
26 + id: Scalars['Int'];
27 + parent: Scalars['Int'];
28 + post_id: Scalars['Int'];
29 + updated_date?: Maybe<Scalars['String']>;
30 +};
31 +export declare type CreateCommentInput = {
32 + content: Scalars['String'];
33 + parent?: Maybe<Scalars['Float']>;
34 + post_id: Scalars['Int'];
35 +};
36 +export declare type CreateLikeableInput = {
37 + like_dislike: Scalars['Int'];
38 + likeable_id: Scalars['Int'];
39 + likeable_type: Scalars['String'];
40 +};
41 +export declare type CreateMyInput = {
42 + name: Scalars['String'];
43 + type?: Maybe<Scalars['String']>;
44 +};
45 +export declare type CreatePostInput = {
46 + category: Scalars['String'];
47 + content: Scalars['String'];
48 + title: Scalars['String'];
49 +};
50 +export declare type GetCommentInput = {
51 + author?: Maybe<Scalars['String']>;
52 + parent?: Maybe<Scalars['String']>;
53 + post_id?: Maybe<Scalars['Float']>;
54 +};
55 +export declare type GetLikeableInput = {
56 + likeable_id?: Maybe<Scalars['Float']>;
57 + likeable_type: Scalars['String'];
58 + user_id?: Maybe<Scalars['String']>;
59 +};
60 +export declare type GetPostInput = {
61 + author?: Maybe<Scalars['String']>;
62 + category?: Maybe<Scalars['String']>;
63 + id?: Maybe<Scalars['Float']>;
64 +};
65 +export declare type Likeable = {
66 + __typename?: 'Likeable';
67 + created_date: Scalars['String'];
68 + id: Scalars['Int'];
69 + like_dislike: Scalars['Int'];
70 + likeable_id: Scalars['Int'];
71 + likeable_type: Scalars['String'];
72 + user_id: Scalars['String'];
73 +};
74 +export declare type Mutation = {
75 + __typename?: 'Mutation';
76 + createComment: Comment;
77 + createLikeable: Likeable;
78 + createMyPage: MyPage;
79 + createPost: Post;
80 +};
81 +export declare type MutationcreateCommentArgs = {
82 + input: CreateCommentInput;
83 +};
84 +export declare type MutationcreateLikeableArgs = {
85 + input: CreateLikeableInput;
86 +};
87 +export declare type MutationcreateMyPageArgs = {
88 + createMyInput: CreateMyInput;
89 +};
90 +export declare type MutationcreatePostArgs = {
91 + input: CreatePostInput;
92 +};
93 +export declare type MyPage = {
94 + __typename?: 'MyPage';
95 + id: Scalars['Int'];
96 + name: Scalars['String'];
97 + type?: Maybe<Scalars['String']>;
98 +};
99 +export declare type Post = {
100 + __typename?: 'Post';
101 + author: Scalars['String'];
102 + category: Scalars['String'];
103 + content: Scalars['String'];
104 + created_date: Scalars['String'];
105 + id: Scalars['Int'];
106 + title: Scalars['String'];
107 + updated_date?: Maybe<Scalars['String']>;
108 +};
109 +export declare type Query = {
110 + __typename?: 'Query';
111 + getAllComments: Array<Comment>;
112 + getAllLikes: Array<Likeable>;
113 + getAllPosts: Array<Post>;
114 + getComment: Comment;
115 + getLikeable: Likeable;
116 + getPost: Post;
117 + getSomeComments: Array<Comment>;
118 + getSomePosts: Array<Post>;
119 + getTotalLikes: Scalars['Float'];
120 + myPage: Array<MyPage>;
121 +};
122 +export declare type QuerygetCommentArgs = {
123 + id: Scalars['Float'];
124 +};
125 +export declare type QuerygetLikeableArgs = {
126 + id: Scalars['Float'];
127 +};
128 +export declare type QuerygetPostArgs = {
129 + id: Scalars['Float'];
130 +};
131 +export declare type QuerygetSomeCommentsArgs = {
132 + input: GetCommentInput;
133 +};
134 +export declare type QuerygetSomePostsArgs = {
135 + input: GetPostInput;
136 +};
137 +export declare type QuerygetTotalLikesArgs = {
138 + input: GetLikeableInput;
139 +};
1 +"use strict";
2 +Object.defineProperty(exports, "__esModule", { value: true });
1 { 1 {
2 "name": "@graphql-community/shared", 2 "name": "@graphql-community/shared",
3 "version": "1.0.0", 3 "version": "1.0.0",
4 + "main": "dist/index.js",
5 + "types": "dist/index.d.ts",
4 "scripts": { 6 "scripts": {
5 - "dev": "next", 7 + "build": "rimraf dist && tsc --build",
6 - "build": "next build",
7 - "start": "next start",
8 "typegen" : "graphql-codegen --config codegen.yml" 8 "typegen" : "graphql-codegen --config codegen.yml"
9 }, 9 },
10 "dependencies": { 10 "dependencies": {
......
1 +export * from "./types/api-generated-types";
1 +{
2 + "compilerOptions": {
3 + "allowJs": true,
4 + "esModuleInterop": true,
5 + "isolatedModules": true,
6 + "jsx": "preserve",
7 + "lib": ["dom", "es2017"],
8 + "module": "commonjs",
9 + "moduleResolution": "node",
10 + "noFallthroughCasesInSwitch": true,
11 + "declaration": true,
12 + "resolveJsonModule": true,
13 + "skipLibCheck": true,
14 + "strict": true,
15 + "target": "es2017",
16 + "outDir" : "./dist",
17 + "rootDir": "./src"
18 + },
19 + "exclude": ["node_modules"],
20 + "include": ["**/*.ts", "**/*.tsx"]
21 + }
22 +
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "presets": ["next/babel"]
3 +}
...@@ -32,3 +32,8 @@ yarn-error.log* ...@@ -32,3 +32,8 @@ yarn-error.log*
32 32
33 # vercel 33 # vercel
34 .vercel 34 .vercel
35 +
36 +# graphql-let
37 +__generated__
38 +*.graphql.d.ts
39 +*.graphqls.d.ts
......
1 +schema: '**/*.graphqls'
2 +schemaEntrypoint: 'lib/type-defs.graphqls'
3 +documents: '**/*.graphql'
4 +plugins:
5 + - typescript
6 + - typescript-operations
7 + - typescript-react-apollo
8 +cacheDir: __generated__
1 -# Apollo Example 1 +# TypeScript and GraphQL Example
2 2
3 -[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run. 3 +One of the strengths of GraphQL is [enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion). Further, TypeScript and [GraphQL Code Generator](https://graphql-code-generator.com/) (graphql-codegen) make it safer by typing data statically, so you can write truly type-protected code with rich IDE assists.
4 4
5 -In this simple example, we integrate Apollo seamlessly with [Next.js data fetching methods](https://nextjs.org/docs/basic-features/data-fetching) to fetch queries in the server and hydrate them in the browser. 5 +This template extends [Apollo Server and Client Example](https://github.com/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client#readme) by rewriting in TypeScript and integrating [graphql-let](https://github.com/piglovesyou/graphql-let#readme), which runs [TypeScript React Apollo](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo) in [graphql-codegen](https://github.com/dotansimha/graphql-code-generator#readme) under the hood. It enhances the typed GraphQL use as below:
6 6
7 -This example relies on [Prisma + Nexus](https://github.com/prisma-labs/nextjs-graphql-api-examples) for its GraphQL backend. 7 +```tsx
8 +import { useNewsQuery } from './news.graphql'
8 9
9 -## Demo 10 +const News = () => {
11 + // Typed already️⚡️
12 + const { data: { news } } = useNewsQuery()
10 13
11 -[https://next-with-apollo.now.sh](https://next-with-apollo.now.sh) 14 + return <div>{news.map(...)}</div>
15 +}
16 +```
17 +
18 +By default `**/*.graphqls` is recognized as GraphQL schema and `**/*.graphql` as GraphQL documents. If you prefer the other extensions, make sure the settings of the webpack loader in `next.config.js` and `.graphql-let.yml` are consistent.
12 19
13 ## Deploy your own 20 ## Deploy your own
14 21
15 Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): 22 Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 23
17 -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-apollo&project-name=with-apollo&repository-name=with-apollo) 24 +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-typescript-graphql&project-name=with-typescript-graphql&repository-name=with-typescript-graphql)
18 25
19 ## How to use 26 ## How to use
20 27
21 Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: 28 Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 29
23 ```bash 30 ```bash
24 -npx create-next-app --example with-apollo with-apollo-app 31 +npx create-next-app --example with-typescript-graphql with-typescript-graphql-app
25 # or 32 # or
26 -yarn create next-app --example with-apollo with-apollo-app 33 +yarn create next-app --example with-typescript-graphql with-typescript-graphql-app
27 ``` 34 ```
28 35
29 Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). 36 Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
......
1 -export default function App({ children }) {
2 - return (
3 - <main>
4 - {children}
5 - <style jsx global>{`
6 - * {
7 - font-family: Menlo, Monaco, 'Lucida Console', 'Liberation Mono',
8 - 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New',
9 - monospace, serif;
10 - }
11 - body {
12 - margin: 0;
13 - padding: 25px 50px;
14 - }
15 - a {
16 - color: #22bad9;
17 - }
18 - p {
19 - font-size: 14px;
20 - line-height: 24px;
21 - }
22 - article {
23 - margin: 0 auto;
24 - max-width: 650px;
25 - }
26 - button {
27 - align-items: center;
28 - background-color: #22bad9;
29 - border: 0;
30 - color: white;
31 - display: flex;
32 - padding: 5px 7px;
33 - transition: background-color 0.3s;
34 - }
35 - button:active {
36 - background-color: #1b9db7;
37 - }
38 - button:disabled {
39 - background-color: #b5bebf;
40 - }
41 - button:focus {
42 - outline: none;
43 - }
44 - `}</style>
45 - </main>
46 - )
47 -}
1 -export default function ErrorMessage({ message }) {
2 - return (
3 - <aside>
4 - {message}
5 - <style jsx>{`
6 - aside {
7 - padding: 1.5em;
8 - font-size: 14px;
9 - color: white;
10 - background-color: red;
11 - }
12 - `}</style>
13 - </aside>
14 - )
15 -}
1 -import { useRouter } from 'next/router'
2 -import Link from 'next/link'
3 -
4 -export default function Header() {
5 - const { pathname } = useRouter()
6 -
7 - return (
8 - <header>
9 - <Link href="/">
10 - <a className={pathname === '/' ? 'is-active' : ''}>Home</a>
11 - </Link>
12 - <Link href="/about">
13 - <a className={pathname === '/about' ? 'is-active' : ''}>About</a>
14 - </Link>
15 - <Link href="/client-only">
16 - <a className={pathname === '/client-only' ? 'is-active' : ''}>
17 - Client-Only
18 - </a>
19 - </Link>
20 - <Link href="/ssr">
21 - <a className={pathname === '/ssr' ? 'is-active' : ''}>SSR</a>
22 - </Link>
23 - <style jsx>{`
24 - header {
25 - margin-bottom: 25px;
26 - }
27 - a {
28 - font-size: 14px;
29 - margin-right: 15px;
30 - text-decoration: none;
31 - }
32 - .is-active {
33 - text-decoration: underline;
34 - }
35 - `}</style>
36 - </header>
37 - )
38 -}
1 -const InfoBox = ({ children }) => (
2 - <div className="info">
3 - <style jsx>{`
4 - .info {
5 - margin-top: 20px;
6 - margin-bottom: 20px;
7 - padding-top: 20px;
8 - padding-bottom: 20px;
9 - border-top: 1px solid #ececec;
10 - border-bottom: 1px solid #ececec;
11 - }
12 - `}</style>
13 - {children}
14 - </div>
15 -)
16 -
17 -export default InfoBox
1 -import { gql, useQuery, NetworkStatus } from '@apollo/client'
2 -import ErrorMessage from './ErrorMessage'
3 -import PostUpvoter from './PostUpvoter'
4 -
5 -export const ALL_POSTS_QUERY = gql`
6 - query allPosts($first: Int!, $skip: Int!) {
7 - allPosts(orderBy: { createdAt: desc }, first: $first, skip: $skip) {
8 - id
9 - title
10 - votes
11 - url
12 - createdAt
13 - }
14 - _allPostsMeta {
15 - count
16 - }
17 - }
18 -`
19 -
20 -export const allPostsQueryVars = {
21 - skip: 0,
22 - first: 10,
23 -}
24 -
25 -export default function PostList() {
26 - const { loading, error, data, fetchMore, networkStatus } = useQuery(
27 - ALL_POSTS_QUERY,
28 - {
29 - variables: allPostsQueryVars,
30 - // Setting this value to true will make the component rerender when
31 - // the "networkStatus" changes, so we are able to know if it is fetching
32 - // more data
33 - notifyOnNetworkStatusChange: true,
34 - }
35 - )
36 -
37 - const loadingMorePosts = networkStatus === NetworkStatus.fetchMore
38 -
39 - const loadMorePosts = () => {
40 - fetchMore({
41 - variables: {
42 - skip: allPosts.length,
43 - },
44 - })
45 - }
46 -
47 - if (error) return <ErrorMessage message="Error loading posts." />
48 - if (loading && !loadingMorePosts) return <div>Loading</div>
49 -
50 - const { allPosts, _allPostsMeta } = data
51 - const areMorePosts = allPosts.length < _allPostsMeta.count
52 -
53 - return (
54 - <section>
55 - <ul>
56 - {allPosts.map((post, index) => (
57 - <li key={post.id}>
58 - <div>
59 - <span>{index + 1}. </span>
60 - <a href={post.url}>{post.title}</a>
61 - <PostUpvoter id={post.id} votes={post.votes} />
62 - </div>
63 - </li>
64 - ))}
65 - </ul>
66 - {areMorePosts && (
67 - <button onClick={() => loadMorePosts()} disabled={loadingMorePosts}>
68 - {loadingMorePosts ? 'Loading...' : 'Show More'}
69 - </button>
70 - )}
71 - <style jsx>{`
72 - section {
73 - padding-bottom: 20px;
74 - }
75 - li {
76 - display: block;
77 - margin-bottom: 10px;
78 - }
79 - div {
80 - align-items: center;
81 - display: flex;
82 - }
83 - a {
84 - font-size: 14px;
85 - margin-right: 10px;
86 - text-decoration: none;
87 - padding-bottom: 0;
88 - border: 0;
89 - }
90 - span {
91 - font-size: 14px;
92 - margin-right: 5px;
93 - }
94 - ul {
95 - margin: 0;
96 - padding: 0;
97 - }
98 - button:before {
99 - align-self: center;
100 - border-style: solid;
101 - border-width: 6px 4px 0 4px;
102 - border-color: #ffffff transparent transparent transparent;
103 - content: '';
104 - height: 0;
105 - margin-right: 5px;
106 - width: 0;
107 - }
108 - `}</style>
109 - </section>
110 - )
111 -}
1 -import { gql, useMutation } from '@apollo/client'
2 -
3 -const UPDATE_POST_MUTATION = gql`
4 - mutation votePost($id: String!) {
5 - votePost(id: $id) {
6 - id
7 - votes
8 - __typename
9 - }
10 - }
11 -`
12 -
13 -export default function PostUpvoter({ votes, id }) {
14 - const [updatePost] = useMutation(UPDATE_POST_MUTATION)
15 -
16 - const upvotePost = () => {
17 - updatePost({
18 - variables: {
19 - id,
20 - },
21 - optimisticResponse: {
22 - __typename: 'Mutation',
23 - votePost: {
24 - __typename: 'Post',
25 - id,
26 - votes: votes + 1,
27 - },
28 - },
29 - })
30 - }
31 -
32 - return (
33 - <button onClick={() => upvotePost()}>
34 - {votes}
35 - <style jsx>{`
36 - button {
37 - background-color: transparent;
38 - border: 1px solid #e4e4e4;
39 - color: #000;
40 - }
41 - button:active {
42 - background-color: transparent;
43 - }
44 - button:before {
45 - align-self: center;
46 - border-color: transparent transparent #000000 transparent;
47 - border-style: solid;
48 - border-width: 0 4px 6px 4px;
49 - content: '';
50 - height: 0;
51 - margin-right: 5px;
52 - width: 0;
53 - }
54 - `}</style>
55 - </button>
56 - )
57 -}
1 -import { gql, useMutation } from '@apollo/client'
2 -
3 -const CREATE_POST_MUTATION = gql`
4 - mutation createPost($title: String!, $url: String!) {
5 - createPost(title: $title, url: $url) {
6 - id
7 - title
8 - votes
9 - url
10 - createdAt
11 - }
12 - }
13 -`
14 -
15 -export default function Submit() {
16 - const [createPost, { loading }] = useMutation(CREATE_POST_MUTATION)
17 -
18 - const handleSubmit = (event) => {
19 - event.preventDefault()
20 - const form = event.target
21 - const formData = new window.FormData(form)
22 - const title = formData.get('title')
23 - const url = formData.get('url')
24 - form.reset()
25 -
26 - createPost({
27 - variables: { title, url },
28 - update: (cache, { data: { createPost } }) => {
29 - cache.modify({
30 - fields: {
31 - allPosts(existingPosts = []) {
32 - const newPostRef = cache.writeFragment({
33 - data: createPost,
34 - fragment: gql`
35 - fragment NewPost on allPosts {
36 - id
37 - type
38 - }
39 - `,
40 - })
41 - return [newPostRef, ...existingPosts]
42 - },
43 - },
44 - })
45 - },
46 - })
47 - }
48 -
49 - return (
50 - <form onSubmit={handleSubmit}>
51 - <h1>Submit</h1>
52 - <input placeholder="title" name="title" type="text" required />
53 - <input placeholder="url" name="url" type="url" required />
54 - <button type="submit" disabled={loading}>
55 - Submit
56 - </button>
57 - <style jsx>{`
58 - form {
59 - border-bottom: 1px solid #ececec;
60 - padding-bottom: 20px;
61 - margin-bottom: 20px;
62 - }
63 - h1 {
64 - font-size: 20px;
65 - }
66 - input {
67 - display: block;
68 - margin-bottom: 10px;
69 - }
70 - `}</style>
71 - </form>
72 - )
73 -}
1 +module.exports = {
2 + roots: ['<rootDir>'],
3 + moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx'],
4 + testPathIgnorePatterns: ['<rootDir>[/\\\\](node_modules|.next)[/\\\\]'],
5 + transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$'],
6 + transform: {
7 + '^.+\\.(ts|tsx)$': 'babel-jest',
8 + '\\.graphql$': [
9 + 'graphql-let/jestTransformer',
10 + { subsequentTransformer: 'babel-jest' },
11 + ],
12 + },
13 +}
1 -import { useMemo } from 'react' 1 +import { useMemo } from "react";
2 -import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client' 2 +import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
3 -import { concatPagination } from '@apollo/client/utilities' 3 +import { concatPagination } from "@apollo/client/utilities";
4 -import merge from 'deepmerge' 4 +import merge from "deepmerge";
5 -import isEqual from 'lodash/isEqual'
6 5
7 -export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__' 6 +export const APOLLO_STATE_PROP_NAME = "__APOLLO_STATE__";
8 7
9 -let apolloClient 8 +let apolloClient: any;
10 9
11 function createApolloClient() { 10 function createApolloClient() {
12 return new ApolloClient({ 11 return new ApolloClient({
13 - ssrMode: typeof window === 'undefined', 12 + ssrMode: typeof window === "undefined",
14 link: new HttpLink({ 13 link: new HttpLink({
15 - uri: 'https://nextjs-graphql-with-prisma-simple.vercel.app/api', // Server URL (must be absolute) 14 + uri: "http://localhost:5000/graphql", // Server URL (must be absolute)
16 - credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers` 15 + credentials: "same-origin", // Additional fetch() options like `credentials` or `headers`
17 }), 16 }),
18 cache: new InMemoryCache({ 17 cache: new InMemoryCache({
19 typePolicies: { 18 typePolicies: {
...@@ -24,50 +23,42 @@ function createApolloClient() { ...@@ -24,50 +23,42 @@ function createApolloClient() {
24 }, 23 },
25 }, 24 },
26 }), 25 }),
27 - }) 26 + });
28 } 27 }
29 28
30 export function initializeApollo(initialState = null) { 29 export function initializeApollo(initialState = null) {
31 - const _apolloClient = apolloClient ?? createApolloClient() 30 + const _apolloClient = apolloClient ?? createApolloClient();
32 31
33 // If your page has Next.js data fetching methods that use Apollo Client, the initial state 32 // If your page has Next.js data fetching methods that use Apollo Client, the initial state
34 // gets hydrated here 33 // gets hydrated here
35 if (initialState) { 34 if (initialState) {
36 // Get existing cache, loaded during client side data fetching 35 // Get existing cache, loaded during client side data fetching
37 - const existingCache = _apolloClient.extract() 36 + const existingCache = _apolloClient.extract();
38 37
39 // Merge the existing cache into data passed from getStaticProps/getServerSideProps 38 // Merge the existing cache into data passed from getStaticProps/getServerSideProps
40 - const data = merge(initialState, existingCache, { 39 + const data = merge(initialState as any, existingCache);
41 - // combine arrays using object equality (like in sets)
42 - arrayMerge: (destinationArray, sourceArray) => [
43 - ...sourceArray,
44 - ...destinationArray.filter((d) =>
45 - sourceArray.every((s) => !isEqual(d, s))
46 - ),
47 - ],
48 - })
49 40
50 // Restore the cache with the merged data 41 // Restore the cache with the merged data
51 - _apolloClient.cache.restore(data) 42 + _apolloClient.cache.restore(data);
52 } 43 }
53 // For SSG and SSR always create a new Apollo Client 44 // For SSG and SSR always create a new Apollo Client
54 - if (typeof window === 'undefined') return _apolloClient 45 + if (typeof window === "undefined") return _apolloClient;
55 // Create the Apollo Client once in the client 46 // Create the Apollo Client once in the client
56 - if (!apolloClient) apolloClient = _apolloClient 47 + if (!apolloClient) apolloClient = _apolloClient;
57 48
58 - return _apolloClient 49 + return _apolloClient;
59 } 50 }
60 51
61 -export function addApolloState(client, pageProps) { 52 +export function addApolloState(client: any, pageProps: any) {
62 if (pageProps?.props) { 53 if (pageProps?.props) {
63 - pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract() 54 + pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
64 } 55 }
65 56
66 - return pageProps 57 + return pageProps;
67 } 58 }
68 59
69 -export function useApollo(pageProps) { 60 +export function useApollo(pageProps: any) {
70 - const state = pageProps[APOLLO_STATE_PROP_NAME] 61 + const state = pageProps[APOLLO_STATE_PROP_NAME];
71 - const store = useMemo(() => initializeApollo(state), [state]) 62 + const store = useMemo(() => initializeApollo(state), [state]);
72 - return store 63 + return store;
73 } 64 }
......
1 +/// <reference types="next" />
2 +/// <reference types="next/types/global" />
3 +
4 +declare module '*.graphqls' {
5 + import { DocumentNode } from 'graphql'
6 + export default typeof DocumentNode
7 +}
8 +
9 +declare module '*.yml'
1 +module.exports = {
2 + webpack(config, options) {
3 + config.module.rules.push({
4 + test: /\.graphql$/,
5 + exclude: /node_modules/,
6 + use: [options.defaultLoaders.babel, { loader: 'graphql-let/loader' }],
7 + })
8 +
9 + config.module.rules.push({
10 + test: /\.graphqls$/,
11 + exclude: /node_modules/,
12 + use: ['graphql-let/schema/loader'],
13 + })
14 +
15 + config.module.rules.push({
16 + test: /\.ya?ml$/,
17 + type: 'json',
18 + use: 'yaml-loader',
19 + })
20 +
21 + return config
22 + },
23 +}
1 { 1 {
2 "name": "@graphql-community/web", 2 "name": "@graphql-community/web",
3 - "version": "1.0.0", 3 + "version": "0.1.0",
4 + "author": "",
5 + "license": "MIT",
4 "scripts": { 6 "scripts": {
5 "dev": "next", 7 "dev": "next",
6 "build": "next build", 8 "build": "next build",
9 + "test": "jest",
7 "start": "next start" 10 "start": "next start"
8 }, 11 },
9 "dependencies": { 12 "dependencies": {
10 - "@apollo/client": "3.1.1", 13 + "@graphql-community/shared": "1.0.0",
11 - "deepmerge": "^4.2.2", 14 + "@apollo/client": "^3.1.3",
12 - "lodash": "4.17.20", 15 + "@graphql-tools/load-files": "6.0.18",
13 - "graphql": "^15.3.0", 16 + "@graphql-tools/merge": "6.0.18",
17 + "@graphql-tools/schema": "6.0.18",
18 + "apollo-server-micro": "^2.16.1",
19 + "graphql": "15.3.0",
14 "next": "latest", 20 "next": "latest",
15 - "prop-types": "^15.6.2", 21 + "react": "^16.13.1",
16 - "react": "^16.7.0", 22 + "react-dom": "^16.13.1"
17 - "react-dom": "^16.7.0"
18 }, 23 },
19 - "license": "MIT" 24 + "devDependencies": {
25 + "@graphql-codegen/cli": "^1.17.8",
26 + "@graphql-codegen/plugin-helpers": "^1.17.8",
27 + "@graphql-codegen/typescript": "^1.17.8",
28 + "@graphql-codegen/typescript-operations": "^1.17.8",
29 + "@graphql-codegen/typescript-react-apollo": "^2.0.6",
30 + "@graphql-codegen/typescript-resolvers": "^1.17.8",
31 + "@types/react": "^16.9.46",
32 + "@types/react-dom": "^16.9.8",
33 + "@types/react-test-renderer": "16.9.3",
34 + "babel-jest": "26.3.0",
35 + "graphql-let": "0.x",
36 + "graphql-tag": "2.11.0",
37 + "jest": "26.4.0",
38 + "react-test-renderer": "16.13.1",
39 + "typescript": "^3.9.7",
40 + "yaml-loader": "0.6.0"
41 + }
20 } 42 }
......
1 -import { ApolloProvider } from '@apollo/client'
2 -import { useApollo } from '../lib/apolloClient'
3 -
4 -export default function App({ Component, pageProps }) {
5 - const apolloClient = useApollo(pageProps)
6 -
7 - return (
8 - <ApolloProvider client={apolloClient}>
9 - <Component {...pageProps} />
10 - </ApolloProvider>
11 - )
12 -}
1 +import { AppProps } from "next/app";
2 +import { ApolloProvider } from "@apollo/client";
3 +import { useApollo } from "../lib/apollo";
4 +
5 +export default function App({ Component, pageProps }: AppProps) {
6 + const apolloClient = useApollo(pageProps);
7 +
8 + return (
9 + <ApolloProvider client={apolloClient}>
10 + <Component {...pageProps} />
11 + </ApolloProvider>
12 + );
13 +}
1 -import App from '../components/App'
2 -import Header from '../components/Header'
3 -
4 -const AboutPage = () => (
5 - <App>
6 - <Header />
7 - <article>
8 - <h1>The Idea Behind This Example</h1>
9 - <p>
10 - <a href="https://www.apollographql.com/client/">Apollo</a> is a GraphQL
11 - client that allows you to easily query the exact data you need from a
12 - GraphQL server. In addition to fetching and mutating data, Apollo
13 - analyzes your queries and their results to construct a client-side cache
14 - of your data, which is kept up to date as further queries and mutations
15 - are run, fetching more results from the server.
16 - </p>
17 - <p>
18 - In this simple example, we integrate Apollo seamlessly with{' '}
19 - <a href="https://github.com/vercel/next.js">Next</a> by calling{' '}
20 - <a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation">
21 - getStaticProps
22 - </a>{' '}
23 - at our Page component. This approach lets us opt out of getInitialProps
24 - and let us use all the niceties provided by{' '}
25 - <a href="https://github.com/vercel/next.js">Next</a>.
26 - </p>
27 - <p>
28 - On initial page load, while on the server and inside{' '}
29 - <a href="https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation">
30 - getStaticProps
31 - </a>
32 - , we fetch the query used to get the list of posts. At the point in
33 - which the query promise resolves, our Apollo Client store is completely
34 - initialized. Then we serve the initial HTML with the fetched data and
35 - hydrate Apollo in the browser.
36 - </p>
37 - <p>
38 - This example relies on <a href="http://graph.cool">graph.cool</a> for
39 - its GraphQL backend.
40 - </p>
41 - </article>
42 - </App>
43 -)
44 -
45 -export default AboutPage
1 +import Link from "next/link";
2 +
3 +export default function About() {
4 + return (
5 + <div>
6 + Welcome to the about page. Go to the{" "}
7 + <Link href="/">
8 + <a>Home</a>
9 + </Link>{" "}
10 + page.
11 + </div>
12 + );
13 +}
1 -import App from '../components/App'
2 -import InfoBox from '../components/InfoBox'
3 -import Header from '../components/Header'
4 -import Submit from '../components/Submit'
5 -import PostList from '../components/PostList'
6 -
7 -const ClientOnlyPage = (props) => (
8 - <App>
9 - <Header />
10 - <InfoBox>
11 - ℹ️ This page shows how to use Apollo only in the client. If you{' '}
12 - <a href="/client-only">reload</a> this page, you will see a loader since
13 - Apollo didn't fetch any data on the server. This is useful when the page
14 - doesn't have SEO requirements or blocking data fetching requirements.
15 - </InfoBox>
16 - <Submit />
17 - <PostList />
18 - </App>
19 -)
20 -
21 -export default ClientOnlyPage
1 -import App from '../components/App'
2 -import InfoBox from '../components/InfoBox'
3 -import Header from '../components/Header'
4 -import Submit from '../components/Submit'
5 -import PostList, {
6 - ALL_POSTS_QUERY,
7 - allPostsQueryVars,
8 -} from '../components/PostList'
9 -import { initializeApollo, addApolloState } from '../lib/apolloClient'
10 -
11 -const IndexPage = () => (
12 - <App>
13 - <Header />
14 - <InfoBox>ℹ️ This page shows how to use SSG with Apollo.</InfoBox>
15 - <Submit />
16 - <PostList />
17 - </App>
18 -)
19 -
20 -export async function getStaticProps() {
21 - const apolloClient = initializeApollo()
22 -
23 - await apolloClient.query({
24 - query: ALL_POSTS_QUERY,
25 - variables: allPostsQueryVars,
26 - })
27 -
28 - return addApolloState(apolloClient, {
29 - props: {},
30 - revalidate: 1,
31 - })
32 -}
33 -
34 -export default IndexPage
1 +import { GetPostInput, Post } from "@graphql-community/shared";
2 +import { useQuery, gql } from "@apollo/client";
3 +
4 +const GET_SOME_POST_QUERY = gql`
5 + query GetSomePosts($getSomePostInput: GetPostInput!) {
6 + getSomePosts(input: $getSomePostInput) {
7 + author
8 + category
9 + }
10 + }
11 +`;
12 +
13 +const Index = () => {
14 + const { data, error } = useQuery<
15 + { getSomePosts: Post[] },
16 + { getSomePostInput: GetPostInput }
17 + >(GET_SOME_POST_QUERY, {
18 + variables: {
19 + getSomePostInput: {
20 + id: 1,
21 + },
22 + },
23 + });
24 + if (error) console.log(JSON.stringify(error, null, 2));
25 +
26 + return (
27 + <>
28 + <div>index </div>
29 + <div>{data?.getSomePosts[0].author}</div>
30 + <div>{data?.getSomePosts[0].category}</div>
31 + </>
32 + );
33 +};
34 +
35 +export default Index;
1 -import App from '../components/App'
2 -import InfoBox from '../components/InfoBox'
3 -import Header from '../components/Header'
4 -import Submit from '../components/Submit'
5 -import PostList, {
6 - ALL_POSTS_QUERY,
7 - allPostsQueryVars,
8 -} from '../components/PostList'
9 -import { initializeApollo, addApolloState } from '../lib/apolloClient'
10 -
11 -const SSRPage = () => (
12 - <App>
13 - <Header />
14 - <InfoBox>ℹ️ This page shows how to use SSR with Apollo.</InfoBox>
15 - <Submit />
16 - <PostList />
17 - </App>
18 -)
19 -
20 -export async function getServerSideProps() {
21 - const apolloClient = initializeApollo()
22 -
23 - await apolloClient.query({
24 - query: ALL_POSTS_QUERY,
25 - variables: allPostsQueryVars,
26 - })
27 -
28 - return addApolloState(apolloClient, {
29 - props: {},
30 - })
31 -}
32 -
33 -export default SSRPage
1 +// Jest Snapshot v1, https://goo.gl/fbAQLP
2 +
3 +exports[`Index renders the html we want 1`] = `
4 +<div>
5 + You're signed in as
6 + Baa
7 + and you're
8 + Healthy
9 + . Go to the
10 +
11 + <a
12 + href="/about"
13 + onClick={[Function]}
14 + onMouseEnter={[Function]}
15 + >
16 + about
17 + </a>
18 +
19 + page.
20 + <div>
21 + <input
22 + onChange={[Function]}
23 + placeholder="your new name..."
24 + type="text"
25 + />
26 + <input
27 + onClick={[Function]}
28 + type="button"
29 + value="change"
30 + />
31 + </div>
32 +</div>
33 +`;
1 +import { InMemoryCache, gql } from '@apollo/client'
2 +import React from 'react'
3 +import Index from '../pages'
4 +import renderer from 'react-test-renderer'
5 +import { MockedProvider } from '@apollo/client/testing'
6 +
7 +const cache = new InMemoryCache()
8 +cache.writeQuery({
9 + query: gql`
10 + query Viewer {
11 + viewer {
12 + id
13 + name
14 + status
15 + }
16 + }
17 + `,
18 + data: {
19 + viewer: {
20 + __typename: 'User',
21 + id: 'Baa',
22 + name: 'Baa',
23 + status: 'Healthy',
24 + },
25 + },
26 +})
27 +
28 +describe('Index', () => {
29 + it('renders the html we want', async () => {
30 + const component = renderer.create(
31 + <MockedProvider cache={cache}>
32 + <Index />
33 + </MockedProvider>
34 + )
35 + expect(component.toJSON()).toMatchSnapshot()
36 + })
37 +})
1 +{
2 + "compilerOptions": {
3 + "allowJs": true,
4 + "esModuleInterop": true,
5 + "forceConsistentCasingInFileNames": true,
6 + "isolatedModules": true,
7 + "jsx": "preserve",
8 + "lib": [
9 + "dom",
10 + "es2017"
11 + ],
12 + "module": "esnext",
13 + "moduleResolution": "node",
14 + "noEmit": true,
15 + "noFallthroughCasesInSwitch": true,
16 + "noUnusedLocals": true,
17 + "noUnusedParameters": true,
18 + "resolveJsonModule": true,
19 + "skipLibCheck": true,
20 + "target": "esnext",
21 + "strict": false
22 + },
23 + "exclude": [
24 + "node_modules"
25 + ],
26 + "include": [
27 + "**/*.ts",
28 + "**/*.tsx"
29 + ]
30 +}
This diff is collapsed. Click to expand it.