Github and Demo
Brief intro about GraphQL and Prisma
GraphQL is developed by Facebook in 2015. On the Client side, It makes nest data fetching easier by JSON like interface (as the image above), rather than multiple URLs or ORM/database request. On the Server side, you can update the data model by add or delete row at the aging field.
Prisma is an alternative ORM and SQL query builder.
The following will show how to build a GraphQL backend from scratch.
prerequisites:
- Node.js installed on your machine
- PostgreSQL database server running
A. setup database:
mkdir todo
mkdir todo/backend
cd todo/backend
npm init -y
npm install @prisma/cli - save-dev
npx prisma init
database model:
code ./prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Post {
id Int @default(autoincrement()) @id
createdAt DateTime @default(now())
title String
content String? //question mark means opational
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int @default(autoincrement()) @id
email String @unique
name String?
password String
posts Post[]
}
code ./prisma/.env
DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE
Prisma Migrate:
npx prisma migrate save --name init --experimental
npx prisma migrate up --experimental
npx prisma generate
B. Build server
npm i graphql-yoga @prisma/client
code index.js
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
const { GraphQLServer } = require('graphql-yoga')
const Query = require('./resolvers/query.js')
const Mutation = require('./resolvers/mutation.js')
const User = require('./resolvers/user.js')
const Post = require('./resolvers/post.js')
const resolvers = {
Query,
Mutation,
User,
Post
}
const server = new GraphQLServer({
typeDefs: './schema.graphql',
resolvers,
context: request => {
return {
...request,
prisma,
}
},
})
const PORT = process.env.PORT || 4000
server.start(PORT, () => console.log(`Server is running on http://localhost:4000`))
- The
typeDefs
connect to the schema wrote at the t section. It defined the database path, data type, and the relation between them. - The
resolvers
defined how to deal with data in each relevant script. For example, Query responsible for fetching data, Mutation for CRUD/ Signup/Login function, we will deal with it later. The rest two (User and Post in resolvers field) define data relations. - The
context
contains custom data being passed through your resolver chain.
C. Build typeDefs, resolvers, and user identification
- define typeDefs
code schema.graphql
type Query {
info: [Post!]!
}
type Mutation {
upsertPost (postId:ID!, title: String!, content: String): Post!
deletePost (postId:ID!): Post
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
}
type Post {
id: ID!
title: String!
content: String
author: User
createdAt: String!
}
type AuthPayload {
token: String
user: User
}
type User {
id: ID!
name: String
email: String!
posts: [Post]
}
- define resolvers
npm i jsonwebtoken bcryptjs dotenv
mkdir resolvers
code ./resolvers/query.js
const { getUserId } = require('../utils')
function info(parent, args, context, info) {
const userId = getUserId(context)
const Posts = context.prisma.post.findMany({
where: {
authorId: parseInt(userId)
}
})
return Posts
}
module.exports = {
info
}
code ./resolvers/mutation.js
const { getUserId } = require('../utils')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
require('dotenv').config()
const APP_SECRET = process.env.APP_SECRET
function upsertPost(parent, args, context, info) {
const userId = getUserId(context)
const upsertPost = context.prisma.post.upsert({
where: {
id: parseInt(args.postId)
},
update: {
title: args.title,
content: args.content,
},
create: {
title: args.title,
content: args.content,
author: {
connect: { id: parseInt(userId) },
},
},
})
return upsertPost
}
function deletePost(parent, args, context, info) {
const deletePost = context.prisma.post.delete({
where: {
id: parseInt(args.postId),
},
})
return deletePost
}
async function signup(parent, args, context, info) {
const password = await bcrypt.hash(args.password, 10)
const user = await context.prisma.user.create({ data: { ...args, password } })
const token = jwt.sign({ userId: user.id }, APP_SECRET)
return {
token,
user,
}
}
async function login(parent, args, context, info) {
const user = await context.prisma.user.findOne({ where: { email: args.email } })
if (!user) {
throw new Error('No such user found')
}
const valid = await bcrypt.compare(args.password, user.password)
if (!valid) {
throw new Error('Invalid password')
}
const token = jwt.sign({ userId: user.id }, APP_SECRET)
return {
token,
user,
}
}
module.exports = {
upsertPost,
deletePost,
signup,
login,
}
code ./resolvers/user.js
function posts(parent, args, context) {
return context.prisma.user.findOne({ where: { id: parent.id } }).posts()
}
module.exports = {
posts,
}
code ./resolvers/post.js
function author(parent, args, context) {
return context.prisma.post.findOne({ where: { id: parent.id } }).author()
}
module.exports = {
author,
}
The four scrips function have short explained on the above second item (The resolvers).
- Build a helper to handle user identification
code utils.js
const jwt = require('jsonwebtoken')
require('dotenv').config()
const APP_SECRET = process.env.APP_SECRET
function getUserId(context) {
const Authorization = context.request.get('Authorization')
if (Authorization) {
const token = Authorization.replace('Bearer ', '')
const { userId } = jwt.verify(token, APP_SECRET)
return userId
}
throw new Error('Not authenticated')
}
module.exports = {
getUserId,
}
code .env
APP_SECRET = Your_APP_SECRET
D. Start GraphQL server
node index.js
you could try the following command on GraphQL left column
- signup
mutation {
signup(
name: "__yourname__"
email: "__your@email.com__"
password: "__yourpassword__"
) {
token
user {
id
}
}
}
- login
mutation {
login(
email: "__your@email.com__"
password: "__yourpassword__"
) {
token
user {
id
name
posts{
id
title
}
}
}
}
- add todo
a. copy token to Header(bottom left)
{ "Authorization": "Bearer __Token__" }
b. command
mutation {
upsertPost(
postId:"0"
title: "www.graphqlconf.org"
) {
id
titile
}
}
- delete todo
mutation {
deletePost(
postId:"__Id__" //enter todo id
) {
id
}
}
Top comments (0)