├── .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 | // } --------------------------------------------------------------------------------