├── .gitignore
├── prisma
├── migrations
│ ├── migration_lock.toml
│ └── 20210411104443_init
│ │ └── migration.sql
└── schema.prisma
├── src
├── graphql
│ ├── resolvers.ts
│ └── schema.ts
├── index.ts
└── utils.ts
├── README.md
├── package.json
├── data
└── seed.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | .env
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/src/graphql/resolvers.ts:
--------------------------------------------------------------------------------
1 | import {getUsers} from '../utils';
2 |
3 |
4 | export const resolvers = {
5 | Query: {
6 | users: async () => getUsers(),
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ApolloServer, gql } from 'apollo-server';
2 | import { typeDefs } from './graphql/schema';
3 | import { resolvers } from './graphql/resolvers';
4 |
5 |
6 |
7 | const server = new ApolloServer({typeDefs, resolvers});
8 |
9 | server.listen().then(({url}:{url: string}) => {
10 | console.log(`Server listening at ${url}`);
11 | })
12 |
13 |
--------------------------------------------------------------------------------
/src/graphql/schema.ts:
--------------------------------------------------------------------------------
1 | const { gql } = require('apollo-server');
2 |
3 | export const typeDefs = gql`
4 | type User {
5 | id: String
6 | name: String
7 | email: String
8 | password: String
9 | projects: [Project]
10 | }
11 |
12 | type Project {
13 | id: Int
14 | title: String
15 | status: String
16 | members: [User]
17 | }
18 |
19 | type Query {
20 | users: [User]
21 | }
22 | `;
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraphQL & TypeScript Tutorial
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | - [Part 1 - YouTube](https://youtu.be/NfqDaFN2bnA)
14 | - [Part 2 - YouTube](https://youtu.be/2MBYrIxYU1E)
15 | - [Part 3 - YouTube](https://youtu.be/YudkjAsZJk0)
16 | - [Part 4 - YouTube](https://youtu.be/gt2Z6zAGtc8)
17 | - [Part 5 - YouTube](https://youtu.be/hjQ61H_7YwM)
18 | - [Part 6 - YouTube](https://youtu.be/aqp8B_lekDE)
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graphql-typescript-tutorial",
3 | "version": "1.0.0",
4 | "description": "Source code for the video tutorial \"GraphQL and TypeScript\"",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "ts-node-dev --respawn --transpile-only src/index.ts"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/willjw3/graphql-typescript-tutorial.git"
12 | },
13 | "keywords": [
14 | "graphql",
15 | "typescript",
16 | "apollo"
17 | ],
18 | "author": "willjw3",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/willjw3/graphql-typescript-tutorial/issues"
22 | },
23 | "homepage": "https://github.com/willjw3/graphql-typescript-tutorial#readme",
24 | "dependencies": {
25 | "@prisma/client": "^2.20.1",
26 | "@types/pg": "^7.14.11",
27 | "@types/uuid": "^8.3.0",
28 | "apollo-server": "^2.21.2",
29 | "dotenv": "^8.2.0",
30 | "graphql": "^15.5.0",
31 | "pg": "^8.5.1",
32 | "uuid": "^8.3.2"
33 | },
34 | "devDependencies": {
35 | "@types/node": "^14.14.35",
36 | "prisma": "^2.20.1",
37 | "ts-node": "^9.1.1",
38 | "ts-node-dev": "^1.1.6",
39 | "typescript": "^4.2.3"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/data/seed.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from 'uuid';
2 |
3 | export const users = [
4 | {
5 | id: uuidv4(),
6 | name: 'Octavio Flores',
7 | email: 'oflores@zcorp.com',
8 | password: 'abc123'
9 | },
10 | {
11 | id: uuidv4(),
12 | name: 'Farah Bennis',
13 | email: 'fbennis@zcorp.com',
14 | password: 'bbc123'
15 | },
16 | {
17 | id: uuidv4(),
18 | name: 'Peter Quan',
19 | email: 'pquan@zcorp.com',
20 | password: 'cbc123'
21 | },
22 | ]
23 |
24 | export const projects = [
25 | {
26 | title: 'Site Upgrade - Login Form',
27 | status: 'active'
28 | },
29 | {
30 | title: 'Site Upgrade - User Dashboard',
31 | status: 'active'
32 | },
33 | {
34 | title: 'Server Migration',
35 | status: 'completed'
36 | },
37 | ]
38 |
39 | export const assignments = [
40 | {
41 | projectId: 1,
42 | userId: '03318b4c-df32-4050-8883-a08eca2beace'
43 | },
44 | {
45 | projectId: 2,
46 | userId: '03318b4c-df32-4050-8883-a08eca2beace'
47 | },
48 | {
49 | projectId: 1,
50 | userId: '9ac802d5-8b61-4314-96f4-7f76612d26c0'
51 | },
52 | ]
53 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "postgresql"
7 | url = env("DATABASE_URL")
8 | }
9 |
10 | model Project {
11 | id Int @id @default(autoincrement())
12 | title String @db.VarChar(255)
13 | status String @db.VarChar(255)
14 | createdAt DateTime @default(now())
15 | updatedAt DateTime @updatedAt
16 | members ProjectAssignment[]
17 | }
18 |
19 | model ProjectAssignment {
20 | createdAt DateTime @default(now())
21 | updatedAt DateTime @updatedAt
22 | projectId Int
23 | userId String @db.Uuid
24 | project Project @relation(fields: [projectId], references: [id])
25 | user User @relation(fields: [userId], references: [id])
26 |
27 | @@id([projectId, userId])
28 | }
29 |
30 | model User {
31 | id String @id @db.Uuid
32 | name String @db.VarChar(255)
33 | email String @unique @db.VarChar(255)
34 | password String @db.VarChar(255)
35 | createdAt DateTime @default(now())
36 | updatedAt DateTime @updatedAt
37 | projects ProjectAssignment[]
38 | }
39 |
--------------------------------------------------------------------------------
/prisma/migrations/20210411104443_init/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Project" (
3 | "id" SERIAL NOT NULL,
4 | "title" VARCHAR(255) NOT NULL,
5 | "status" VARCHAR(255) NOT NULL,
6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7 | "updatedAt" TIMESTAMP(3) NOT NULL,
8 |
9 | PRIMARY KEY ("id")
10 | );
11 |
12 | -- CreateTable
13 | CREATE TABLE "ProjectAssignment" (
14 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
15 | "updatedAt" TIMESTAMP(3) NOT NULL,
16 | "projectId" INTEGER NOT NULL,
17 | "userId" UUID NOT NULL,
18 |
19 | PRIMARY KEY ("projectId","userId")
20 | );
21 |
22 | -- CreateTable
23 | CREATE TABLE "User" (
24 | "id" UUID NOT NULL,
25 | "name" VARCHAR(255) NOT NULL,
26 | "email" VARCHAR(255) NOT NULL,
27 | "password" VARCHAR(255) NOT NULL,
28 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
29 | "updatedAt" TIMESTAMP(3) NOT NULL,
30 |
31 | PRIMARY KEY ("id")
32 | );
33 |
34 | -- CreateIndex
35 | CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
36 |
37 | -- AddForeignKey
38 | ALTER TABLE "ProjectAssignment" ADD FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
39 |
40 | -- AddForeignKey
41 | ALTER TABLE "ProjectAssignment" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | // "lib": [], /* Specify library files to be included in the compilation. */
10 | "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "./build", /* Redirect output structure to the directory. */
18 | "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 |
44 | /* Module Resolution Options */
45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
49 | // "typeRoots": [], /* List of folders to include type definitions from. */
50 | // "types": [], /* Type declaration files to be included in compilation. */
51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
55 |
56 | /* Source Map Options */
57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
61 |
62 | /* Experimental Options */
63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
65 |
66 | /* Advanced Options */
67 | "skipLibCheck": true, /* Skip type checking of declaration files. */
68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
69 | },
70 | "exclude": ["node_modules"],
71 | "include": [
72 | "./**/*.ts"
73 | , "graphql/schema.js" ]
74 | }
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import {users, projects, assignments} from '../data/seed';
2 |
3 | import { PrismaClient } from '@prisma/client'
4 |
5 | const prisma = new PrismaClient()
6 |
7 | export const getUsers = async () => {
8 | const result = await prisma.user.findMany({
9 | include: { projects: { include: { project: true } } },
10 | })
11 | const allUsers = result.map(user => {
12 | return { ...user, projects: user.projects.map(assignment => assignment.project) }
13 | })
14 | return allUsers
15 | }
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | // import { pool } from "../database/db";
35 | // import { v4 as uuidv4 } from 'uuid';
36 |
37 |
38 | // interface User {
39 | // id: string
40 | // name: string
41 | // email: string
42 | // password: string
43 | // }
44 |
45 | // export const readQuery = async (query: string) => {
46 |
47 | // try {
48 | // const res = await pool.query(query);
49 | // return res.rows;
50 | // } catch (err) {
51 | // console.error(err);
52 | // }
53 | // }
54 |
55 | // export const getUsersFromUsersTable = async () => {
56 | // const query = `
57 | // SELECT * FROM users;
58 | // `;
59 |
60 | // return readQuery(query);
61 |
62 | // }
63 |
64 | // export const getAssignments = async () => {
65 | // const query = `
66 | // SELECT assignments.user_id, users.name, assignments.project_id, projects.title, projects.status
67 | // FROM ((users
68 | // INNER JOIN assignments
69 | // ON users.id = assignments.user_id)
70 | // INNER JOIN projects
71 | // ON assignments.project_id = projects.id);
72 | // `;
73 |
74 | // return readQuery(query);
75 | // }
76 |
77 | // export const findUserByEmail = async (email: string) => {
78 | // const query = {
79 | // text: 'SELECT * FROM users WHERE email = $1',
80 | // values: [email]
81 | // }
82 |
83 | // try {
84 | // const res = await pool.query(query);
85 | // return res.rows;
86 | // } catch (err) {
87 | // console.error(err);
88 | // }
89 | // return [{
90 | // id: '0',
91 | // name: "J Doe",
92 | // email: 'jdoe@zcorp.com',
93 | // password: 'passwordz'
94 | // }]
95 | // }
96 |
97 |
98 |
99 | // export const addUser = async (name: string, email: string, password: string) => {
100 |
101 | // const query = {
102 | // text: 'INSERT INTO users(id, name, email, password) VALUES($1, $2, $3, $4)',
103 | // values: [uuidv4(), name, email, password],
104 | // }
105 |
106 |
107 | // const userQuery = await findUserByEmail(email);
108 | // if (userQuery?.length === 0) {
109 | // try {
110 | // const res = await pool.query(query);
111 | // console.log("User added.");
112 | // } catch (err) {
113 | // console.error(err);
114 | // }
115 | // } else {
116 | // console.log("Unable to add user. Check your email address");
117 | // }
118 | // }
119 |
120 | // export const addProject = async (title: string, status: string) => {
121 | // const query = {
122 | // text: 'INSERT INTO projects(title, status) VALUES($1, $2)',
123 | // values: [title, status],
124 | // }
125 |
126 | // try {
127 | // const res = await pool.query(query);
128 | // console.log("Project added.");
129 | // } catch (err) {
130 | // console.error(err);
131 | // }
132 | // }
133 |
134 | // export const addAssignment = async (user_email: string, project_id: number, user_name: string) => {
135 | // let user_id: string = '';
136 | // const user = await findUserByEmail(user_email);
137 | // if (user?.length === 0) {
138 | // console.log("User with that email not found.")
139 | // } else {
140 | // user_id = user[0].id;
141 | // }
142 |
143 | // const query = {
144 | // text: 'INSERT INTO assignments(project_id, user_id, user_name) VALUES($1, $2, $3)',
145 | // values: [project_id, user_id, user_name],
146 | // }
147 |
148 | // try {
149 | // const res = await pool.query(query);
150 | // console.log('Assignment created.');
151 | // } catch (err) {
152 | // console.error(err);
153 | // }
154 | // }
155 |
156 | // export const getUsers = async () => {
157 | // const usersFromUsersTable = await getUsersFromUsersTable();
158 | // const assignments = await getAssignments();
159 | // const users = await usersFromUsersTable?.map(async (user) => {
160 | // let projects: object [] = [];
161 | // await assignments?.forEach(assignment => {
162 | // if (user.id === assignment.user_id) {
163 | // projects.push({
164 | // id: assignment.project_id,
165 | // title: assignment.title,
166 | // status: assignment.status
167 | // });
168 | // }
169 | // })
170 | // return {
171 | // id: user.id,
172 | // name: user.name,
173 | // email: user.email,
174 | // projects: projects,
175 | // }
176 | // })
177 | // return users;
178 | // }
179 |
180 | // export const createUserTable = async () => {
181 |
182 | // const query = `
183 | // CREATE TABLE users (
184 | // id varchar primary key,
185 | // name varchar,
186 | // email varchar,
187 | // password varchar
188 | // )
189 | // `;
190 |
191 | // return readQuery(query);
192 | // }
193 |
194 | // export const createProjectTable = async () => {
195 |
196 | // const query = `
197 | // CREATE TABLE projects (
198 | // id serial primary key,
199 | // title varchar,
200 | // status varchar
201 | // )
202 | // `;
203 |
204 | // return readQuery(query) ? "Table created." : "Unable to create table.";
205 | // }
206 |
207 | // export const createAssignmentTable = async () => {
208 |
209 | // const query = `
210 | // CREATE TABLE assignments (
211 | // id serial,
212 | // project_id int references projects (id),
213 | // user_id varchar references users (id),
214 | // primary key (project_id, user_id),
215 | // user_name varchar
216 | // )
217 | // `;
218 |
219 | // return readQuery(query) ? "Table created" : "Unable to create table.";
220 | // }
221 |
222 | // export const listTables = async () => {
223 | // const query = `
224 | // SELECT table_name
225 | // FROM information_schema.tables
226 | // WHERE table_schema = 'public'
227 | // ORDER BY table_name;
228 | // `
229 | // try {
230 | // const res = await pool.query(query);
231 | // console.log(res.rows)
232 | // } catch (err) {
233 | // console.error(err)
234 | // }
235 | // }
--------------------------------------------------------------------------------