Skip to content
This repository has been archived by the owner on Jun 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1 from NicolasOmar/lesson/data-storage-with-prisma
Browse files Browse the repository at this point in the history
Lesson #2 | Data handling using ith prisma
  • Loading branch information
NicolasOmar authored Jan 25, 2022
2 parents 4d294b2 + 89245f2 commit 37e68d0
Show file tree
Hide file tree
Showing 17 changed files with 4,623 additions and 5 deletions.
2 changes: 1 addition & 1 deletion 1-exercises/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-practice-exercises",
"version": "0.0.5",
"version": "1.0.0",
"author": "Nicolás Omar González Passerino",
"license": "MIT",
"private": false,
Expand Down
8 changes: 8 additions & 0 deletions 3-prisma/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
3 changes: 3 additions & 0 deletions 3-prisma/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
# Keep environment variables out of version control
.env
4,306 changes: 4,306 additions & 0 deletions 3-prisma/package-lock.json

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions 3-prisma/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "graphql-practice-prisma",
"version": "1.0.0",
"author": "Nicolás Omar González Passerino",
"license": "MIT",
"private": false,
"scripts": {
"start": "nodemon src/index.js --ext js,graphql --exec babel-node",
"setup": "npm i -g prisma && npm i && npm run generate-schema",
"test": "jest",
"generate-schema": "prisma db pull && prisma generate",
"sync-schema": "prisma db push --accept-data-loss --schema=./src/prisma/schema.prisma"
},
"dependencies": {
"@babel/cli": "^7.16.8",
"@babel/core": "^7.16.10",
"@babel/node": "^7.16.8",
"@babel/plugin-transform-runtime": "^7.16.10",
"@babel/preset-env": "^7.16.11",
"@prisma/client": "^3.8.1",
"apollo-server": "^3.6.2",
"bcrypt": "^5.0.1",
"graphql": "^16.2.0",
"jsonwebtoken": "^8.5.1",
"prisma": "^3.8.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
18 changes: 18 additions & 0 deletions 3-prisma/src/graphql/resolvers/mutations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import bcrypt from 'bcrypt'
// UTILS
import { authUser } from '../utils/auth'
import { prismaCreate, prismaDelete } from '../utils/prisma'

const Mutation = {
createUser: async (_, { userData }, { prisma }) => {
const password = (await bcrypt.hash(userData.password, 5))
return await prismaCreate('user', { ...userData, password }, prisma)
},
createBook: async (_, { bookData }, { prisma, request }) => authUser(request) && await prismaCreate('book', bookData, prisma),
createReview: async(_, { reviewData }, { prisma, request }) => authUser(request) && await prismaCreate('review', reviewData, prisma),
deleteUser: async (_, { id }, { prisma, request }) => authUser(request) && await prismaDelete('user', id, prisma),
deleteBook: async (_, { id }, { prisma, request }) => authUser(request) && await prismaDelete('book', id, prisma),
deleteReview: async(_, { id }, { prisma, request }) => authUser(request) && await prismaDelete('review', id, prisma)
}

export default Mutation
19 changes: 19 additions & 0 deletions 3-prisma/src/graphql/resolvers/queries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import bcrypt from 'bcrypt'
// UTILS
import { authUser } from '../utils/auth'
import { prismaRead } from '../utils/prisma'

const Queries = {
getAllUsers: async (_, { query }, { prisma, request }) => authUser(request) && await prismaRead({ prisma, entity: 'user', ...query }),
getAllBooks: async (_, { query }, { prisma, request }) => authUser(request) && await prismaRead({ prisma, entity: 'book', ...query }),
getAllReviews: async (_, { query }, { prisma, request }) => authUser(request) && await prismaRead({ prisma, entity: 'review', ...query }),
getUserById: async (_, { id }, { prisma, request }) => authUser(request) && await prismaRead({ prisma, entity: 'user', id }),
getBookById: async (_, { id }, { prisma, request }) => authUser(request) && await prismaRead({ prisma, entity: 'book', id }),
getReviewById: async (_, { id }, { prisma, request }) => authUser(request) && await prismaRead({ prisma, entity: 'review', id }),
loginUser: async (_, { userData }, { prisma }) => {
const user = await prisma.user.findUnique({ where: { username: userData.username }})
return (await bcrypt.compare(userData.password, user.password) ? user : null)
}
}

export default Queries
17 changes: 17 additions & 0 deletions 3-prisma/src/graphql/resolvers/relationships.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { authUser } from "../utils/auth"

const Relationships = {
User: {
password: ({ password }, __, { request }) => authUser(request) ? password : null,
reviews: async ({ id }, _, { prisma }) => await prisma.review.findMany({ where: { userId: id }})
},
Book: {
reviews: async ({ id }, _, { prisma }) => await prisma.review.findMany({ where: { bookId: id } })
},
Review: {
author: async ({ userId }, _, { prisma }) => await prisma.user.findUnique({ where: { id: userId}}),
book: async ({ bookId }, _, { prisma }) => await prisma.book.findUnique({ where: { id: bookId}})
}
}

export default Relationships
74 changes: 74 additions & 0 deletions 3-prisma/src/graphql/typeDefs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { gql } from "apollo-server";

const typeDefs = gql`
type Query {
getAllUsers(query: queryInput): [User]!
getAllBooks(query: queryInput): [Book]!
getAllReviews(query: queryInput): [Review]!
getUserById(id: String!): User!
getBookById(id: String!): Book!
getReviewById(id: String!): Review!
loginUser(userData: CreateUserInput!): User!
}
type Mutation {
createUser(userData: CreateUserInput!): User!
createBook(bookData: CreateBookInput!): Book!
createReview(reviewData: CreateReviewInput!): Review!
deleteUser(id: String!): User!
deleteBook(id: String!): Book!
deleteReview(id: String!): Review!
}
type User {
id: String!
username: String!
password: String!
reviews: [Review]!
}
type Book {
id: String!
isbn: String
title: String
author: String!
reviews: [Review]!
}
type Review {
id: String!
text: String
rating: Int!
userId: String!
bookId: String!
author: User!
book: Book!
}
input queryInput {
page: Int
rows: Int
orderBy: String
order: String
}
input CreateUserInput {
username: String!
password: String!
}
input CreateBookInput {
isbn: String
title: String
author: String!
}
input CreateReviewInput {
text: String
rating: Int!
userId: String!
bookId: String!
}
`

export default typeDefs
12 changes: 12 additions & 0 deletions 3-prisma/src/graphql/utils/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import jwt from 'jsonwebtoken'
import { ApolloError } from 'apollo-server'

export const authUser = (request) => {
const token = request.headers['auth']
? request.headers['auth'].replace('Bearer ', '')
: null

if (!token || !(jwt.verify(token, process.env.JWT_SECRET))) throw new ApolloError('Unauthenticated user')

return true
}
31 changes: 31 additions & 0 deletions 3-prisma/src/graphql/utils/prisma.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { v4 as uuid } from 'uuid'

const constructConfig = ({ id = null, page = 1, rows = 1, orderBy = 'id', order = 'desc' }) => (
{
unique: {
fn: 'findUnique',
config: {
where: { id }
}
},
all: {
fn: 'findMany',
config: {
take: rows,
skip: page === 1 ? 0 : ((page - 1) * rows),
orderBy: { [orderBy]: order }
}
}
}
)

export const prismaRead = ({ prisma, entity, id = null, page = 1, rows = 1, orderBy = 'id', order = 'desc' }) => {
const { unique, all } = constructConfig({ id, page, rows, orderBy, order })
const { fn, config } = id ? unique : all

return prisma[entity][fn]({ ...config })
}

export const prismaCreate = (entity, payload, prisma) => prisma[entity].prismaCreate({ data: { ...payload, id: uuid() } })

export const prismaDelete = (entity, id, prisma) => prisma[entity].delete({ where: { id } })
24 changes: 24 additions & 0 deletions 3-prisma/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApolloServer } from 'apollo-server'
import { PrismaClient } from '@prisma/client'
// SCHEMA
import typeDefs from './graphql/typeDefs'
// RESOLVERS
import Query from './graphql/resolvers/queries'
import Mutation from './graphql/resolvers/mutations'
import Relationships from './graphql/resolvers/relationships'

const prisma = new PrismaClient()

const server = new ApolloServer({
typeDefs,
resolvers: {
Query,
Mutation,
...Relationships
},
context: async ({ req }) => (
{ prisma, request: req }
)
})

server.listen().then(async ({ url }) => console.log(`Welcome to the page ${url}`))
33 changes: 33 additions & 0 deletions 3-prisma/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id String @id @db.Uuid @unique
username String @db.VarChar @unique
password String
reviews Review[]
}

model Book {
id String @id @db.Uuid @unique
isbn String @db.VarChar
title String @db.VarChar
author String @db.VarChar
reviews Review[]
}

model Review {
id String @id @db.Uuid @unique
text String @db.VarChar
rating Int @db.Integer
userId String @db.Uuid
bookId String @db.Uuid
author User @relation(fields: [userId], references: [id], onDelete: Cascade)
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
}
Empty file added 3-prisma/tests/user.test.js
Empty file.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ Repository created to record my practice learning GraphQL with exercises based o

## Requirements
- [Node](https://nodejs.org/en/download/) v12.16.1 or above
- A relational database (like PostgreSQL or MySQL, for example)
- [pgAdmin](https://www.pgadmin.org/) or any relational database client (for `3-prisma`)

## Repo Structure
- `1-exercises`: Exercises related to [Andrew's course](https://www.udemy.com/course/graphql-bootcamp).
- `2-apollo`: Server and Client projects from [Apollo's Tutorial](https://www.apollographql.com/docs/tutorial/introduction/) to understand an end to end implementation.
- `3-prisma`: [Prisma](https://www.prisma.io/) learning and integration with a GraphQL server.

## Setup
After cloning the repo, go to the created folder and install the node packages.
Expand All @@ -20,7 +23,8 @@ npm run setup-all
| --- | --- |
| All | `npm run setup-all` |
| Exercises | `npm run setup-exercises` |
| Apollo Tutorial | `npm run setup-apollo` |
| Apollo tutorial | `npm run setup-apollo` |
| Prisma exercises | `npm run setup-prisma` |

## What did I learn?
- Set and run a server using a minimum configuration
Expand All @@ -33,9 +37,19 @@ npm run setup-all
- Understanding, and configuration of `Relational data` through `Types`
- How to make a `Mutation`
- Configure `Input Types` to improve readability in Mutation operators
- How to make a `Subscription`
- Structure a good `folder structure`
- Creation of a `Schema file` to hold created Types (Queries, Mutation, Subscriptions, and Custom Types)
- Split different Resolvers into files by Operation type or Entity
- How to make CRUD operations (CREATE, READ, UPDATE & DELETE) to a SQL-based database using `Prisma`
- Understand how to map the database config using `prisma db pull` && `prisma generate`
- How to use its special annotations in the `schema.prisma` file
- How to make changes to the database using `prisma db push`
- Integrate the tool in a Node/GraphQL server
- Hash and compare hashed passwords using [bcrypt](https://www.npmjs.com/package/bcrypt)
- Create JWT and verify tokens using [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken)
- Paginate results by using `take` and `skip` properties in queries
- Sort the results by using `orderBy` and `order`

## Version (currently ![GraphQL Practice](https://img.shields.io/github/package-json/v/nicolasomar/graphql-practice?color=success&label=%20&style=flat-square))
| Number | Meaning |
Expand Down
28 changes: 27 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 37e68d0

Please sign in to comment.