type Query {
human(id: ID!): Human
}
type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Starship {
name: String
}
---------------------------------------
query:
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}
result:
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}
--------------------------------------
resolvers fields
Query: {
human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
}
obj The previous object, which for a field on the root Query type is often not used.
args The arguments provided to the field in the GraphQL query.
context A value which is provided to every resolver and holds important contextual information like the currently logged in user, or access to a database.
info A value which holds field-specific information relevant to the current query as well as the schema details, also refer type GraphQLResolveInfo for more details.
-------------------------------------
root resolver
Human: {
name(obj, args, context, info) {
return obj.name
}
}
---------------------------------
scalar resolver
Human: {
appearsIn(obj) {
return obj.appearsIn // returns [ 4, 5, 6 ]
}
}
-----------------------------------
list resolver
Human: {
starships(obj, args, context, info) {
return obj.starshipIDs.map(
id => context.db.loadStarshipByID(id).then(
shipData => new Starship(shipData)
)
)
}
}
----------------------------------
reference:
https://graphql.org/learn/execution/
Thursday, 6 September 2018
Wednesday, 5 September 2018
Monday, 3 September 2018
graphql authentication server side
--index.js
--schema.graphql
--utils
--resolvers
--Feed.js
--Mutation.js
--Query.js
--------------------------------------
index.js
const { GraphQLServer } = require('graphql-yoga')
const { Prisma } = require('prisma-binding')
const Query = require('./resolvers/Query')
const Mutation = require('./resolvers/Mutation')
const Subscription = require('./resolvers/Subscription')
const Feed = require('./resolvers/Feed')
const resolvers = {
Query,
Mutation,
Subscription,
Feed,
}
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context: req => ({
...req,
db: new Prisma({
typeDefs: 'src/generated/prisma.graphql',
endpoint: 'https://us1.prisma.sh/bob-722fd9/auth1/a1',
secret: 'mysecret123',
debug: true
}),
}),
})
server.start(() => console.log('Server is running on http://localhost:4000'))
--------------------------------------------------
schema.graphql
# import Link, Vote, LinkSubscriptionPayload, VoteSubscriptionPayload from "./generated/prisma.graphql"
type Query {
feed(filter: String, skip: Int, first: Int, orderBy: LinkOrderByInput): Feed!
}
type Feed {
links: [Link!]!
count: Int!
}
type Mutation {
post(url: String!, description: String!): Link!
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
vote(linkId: ID!): Vote
}
type AuthPayload {
token: String
user: User
}
type User {
id: ID!
name: String!
email: String!
}
type Subscription {
newLink: LinkSubscriptionPayload
newVote: VoteSubscriptionPayload
}
---------------------------------------------
utils.js
const jwt = require('jsonwebtoken')
const APP_SECRET = 'GraphQL-is-aw3some'
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 = {
APP_SECRET,
getUserId,
}
-------------------------------------------
Feed.js
function links(parent, args, context, info) {
const { linkIds } = parent
return context.db.query.links({ where: { id_in: linkIds } }, info)
}
module.exports = {
links,
}
------------------------------------------------
Query.js
async function feed(parent, args, ctx, info) {
const { filter, first, skip } = args // destructure input arguments
const where = filter
? { OR: [{ url_contains: filter }, { description_contains: filter }] }
: {}
const allLinks = await ctx.db.query.links({})
const count = allLinks.length
const queriedLinkes = await ctx.db.query.links({ first, skip, where })
return {
linkIds: queriedLinkes.map(link => link.id),
count
}
}
module.exports = {
feed,
}
-----------------------------------------------
Mutation.js
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const { APP_SECRET, getUserId } = require('../utils')
function post(parent, { url, description }, ctx, info) {
const userId = getUserId(ctx)
return ctx.db.mutation.createLink(
{ data: { url, description, postedBy: { connect: { id: userId } } } },
info,
)
}
async function signup(parent, args, ctx, info) {
const password = await bcrypt.hash(args.password, 10)
const user = await ctx.db.mutation.createUser({
data: { ...args, password },
})
const token = jwt.sign({ userId: user.id }, APP_SECRET)
return {
token,
user,
}
}
async function login(parent, args, ctx, info) {
const user = await ctx.db.query.user({ 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')
}
return {
token: jwt.sign({ userId: user.id }, APP_SECRET),
user,
}
}
async function vote(parent, args, ctx, info) {
const { linkId } = args
const userId = getUserId(ctx)
const linkExists = await ctx.db.exists.Vote({
user: { id: userId },
link: { id: linkId },
})
if (linkExists) {
throw new Error(`Already voted for link: ${linkId}`)
}
return ctx.db.mutation.createVote(
{
data: {
user: { connect: { id: userId } },
link: { connect: { id: linkId } },
},
},
info,
)
}
module.exports = {
post,
signup,
login,
vote,
}
--------------------------------
reference:
http://chuanshuoge2.blogspot.com/2018/08/how-to-graphql.html
https://chuanshuoge2.blogspot.com/2018/08/jwt-authentication.html
reference:
http://chuanshuoge2.blogspot.com/2018/08/how-to-graphql.html
https://chuanshuoge2.blogspot.com/2018/08/jwt-authentication.html
graphql authentication client side
--index.js
--constants.js
--components
--App.js
--CreateLink.js
--Header.js
--Link.js
--LinkList.js
--Login.js
----------------------------------
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './styles/index.css'
import App from './components/App'
import registerServiceWorker from './registerServiceWorker'
import { ApolloProvider } from 'react-apollo'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { BrowserRouter } from 'react-router-dom'
import { setContext } from 'apollo-link-context'
import { AUTH_TOKEN } from './constants'
const httpLink = createHttpLink({
uri: 'http://localhost:4000'
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(AUTH_TOKEN)
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
ReactDOM.render(
<BrowserRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</BrowserRouter>,
document.getElementById('root')
)
registerServiceWorker()
----------------------------------------------------
constants.js
export const AUTH_TOKEN = 'auth-token'
---------------------------------------------------------
App.js
import React, { Component } from 'react';
import '../styles/App.css';
import LinkList from './LinkList'
import CreateLink from './CreateLink'
import Header from './Header'
import { Switch, Route } from 'react-router-dom'
import Login from './Login'
class App extends Component {
render() {
return (
<div className="center w85">
<Header />
<div className="ph3 pv1 background-gray">
<Switch>
<Route exact path="/" component={LinkList} />
<Route exact path="/create" component={CreateLink} />
<Route exact path="/login" component={Login} />
</Switch>
</div>
</div>
);
}
}
export default App;
-------------------------------------------------------
createLinks.js
import React, { Component } from 'react'
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
const POST_MUTATION = gql`
mutation PostMutation($description: String!, $url: String!) {
post(description: $description, url: $url) {
id
createdAt
url
description
}
}
`
class CreateLink extends Component {
state = {
description: '',
url: '',
}
render() {
const { description, url } = this.state
return (
<div>
<div className="flex flex-column mt3">
<input
className="mb2"
value={description}
onChange={e => this.setState({ description: e.target.value })}
type="text"
placeholder="A description for the link"
/>
<input
className="mb2"
value={url}
onChange={e => this.setState({ url: e.target.value })}
type="text"
placeholder="The URL for the link"
/>
</div>
<Mutation mutation={POST_MUTATION} variables={{ description, url }}
onCompleted={() => this.props.history.push('/')}>
{postMutation => <button onClick={postMutation}>Submit</button>}
</Mutation>
</div>
)
}
}
export default CreateLink
----------------------------------------
Header.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { withRouter } from 'react-router'
import { AUTH_TOKEN } from '../constants'
class Header extends Component {
render() {
const authToken = localStorage.getItem(AUTH_TOKEN)
return (
<div className="flex pa1 justify-between nowrap orange">
<div className="flex flex-fixed black">
<div className="fw7 mr1">Hacker News</div>
<Link to="/" className="ml1 no-underline black">
new
</Link>
<div className="ml1">|</div>
<Link to="/create" className="ml1 no-underline black">
submit
</Link>
</div>
<div className="flex flex-fixed">
{authToken ? (
<div
className="ml1 pointer black"
onClick={() => {
localStorage.removeItem(AUTH_TOKEN)
this.props.history.push(`/`)
}}
>
logout
</div>
) : (
<Link to="/login" className="ml1 no-underline black">
login
</Link>
)}
</div>
</div>
)
}
}
export default withRouter(Header)
----------------------------------------------
link.js
import React, { Component } from 'react'
class Link extends Component {
render() {
return (
<div>
<div>
{this.props.link.description} ({this.props.link.url})
</div>
</div>
)
}
}
export default Link
--------------------------------------
linklist.js
import React, { Component } from 'react'
import Link from './Link'
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
const FEED_QUERY = gql`
{
feed {
links {
id
createdAt
url
description
}
}
}
`
class LinkList extends Component {
render() {
return (
<Query query={FEED_QUERY}>
{({ loading, error, data }) => {
if (loading) return <div>Fetching</div>
if (error) return <div>Error</div>
const linksToRender = data.feed.links
return (
<div>
{linksToRender.map(link => <Link key={link.id} link={link} />)}
</div>
)
}}
</Query>
)
}
}
export default LinkList
----------------------------------------
login.js
import React, { Component } from 'react'
import { AUTH_TOKEN } from '../constants'
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
const SIGNUP_MUTATION = gql`
mutation SignupMutation($email: String!, $password: String!, $name: String!) {
signup(email: $email, password: $password, name: $name) {
token
}
}
`
const LOGIN_MUTATION = gql`
mutation LoginMutation($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`
class Login extends Component {
state = {
login: true, // switch between Login and SignUp
email: '',
password: '',
name: '',
}
render() {
const { login, email, password, name } = this.state
return (
<div>
<h4 className="mv3">{login ? 'Login' : 'Sign Up'}</h4>
<div className="flex flex-column">
{!login && (
<input
value={name}
onChange={e => this.setState({ name: e.target.value })}
type="text"
placeholder="Your name"
/>
)}
<input
value={email}
onChange={e => this.setState({ email: e.target.value })}
type="text"
placeholder="Your email address"
/>
<input
value={password}
onChange={e => this.setState({ password: e.target.value })}
type="password"
placeholder="Choose a safe password"
/>
</div>
<div className="flex mt3">
<Mutation
mutation={login ? LOGIN_MUTATION : SIGNUP_MUTATION}
variables={{ email, password, name }}
onCompleted={data => this._confirm(data)}
>
{mutation => (
<div className="pointer mr2 button" onClick={mutation}>
{login ? 'login' : 'create account'}
</div>
)}
</Mutation>
<div
className="pointer button"
onClick={() => this.setState({ login: !login })}
>
{login
? 'need to create an account?'
: 'already have an account?'}
</div>
</div>
</div>
)
}
_confirm = async data => {
const { token } = this.state.login ? data.login : data.signup
this._saveUserData(token)
this.props.history.push(`/`)
}
_saveUserData = token => {
localStorage.setItem(AUTH_TOKEN, token)
}
}
export default Login
----------------------------------
Tuesday, 28 August 2018
HOW TO GRAPHQL
reference:
https://www.howtographql.com/
https://www.youtube.com/watch?v=_kYUzNVyj-0
https://www.youtube.com/watch?v=lQDrREfapow&list=PLn2e1F9Rfr6kwLZy5BkIde_Gvm0ytB61L
graphcool vs prisma
https://www.prisma.io/forum/t/graphcool-framework-vs-graphcool-v1-beta-vs-prisma/2174
https://www.prisma.io/
pagination
https://graphql.org/learn/pagination/
connections
https://blog.apollographql.com/explaining-graphql-connections-c48b7c3d6976
prisma resolvers
https://www.prisma.io/docs/tutorials/build-graphql-servers/development/build-a-graphql-server-with-prisma-ohdaiyoo6c
data model (user, post...) -> schema (query, mutation...) -> resolver (implement schema) action:(_, args, ...){return context.prisma.mutation/query.action{...} }->
Sunday, 26 August 2018
JWT authentication
get token from JWT.sign
if token is not included in authorization, post request is denied
if token is validated by JWT.verify, post request gets data back
toke expires after the period in JWT.sign expireIn, post request is rejected.
1. install postman https://www.getpostman.com/
2. npm init, entry point app.js
3. npm install express jsonwebtoken
4. npm install -g nodemon
5. create app.js
6. send post request to localhost:5000/api/login with postman to get token
7. send post request to localhost:5000/api/posts with token in authorization header
app.js
const express = require('express');const jwt = require('jsonwebtoken');
const app = express();
app.get('/api', (req, res) => {
res.json({
message: 'welcome to the api'
});
});
app.post('/api/posts', verifyToken, (req, res) => {
jwt.verify(req.token, 'secretkey', (err, authData) => {
if (err) {
res.sendStatus(403);
} else {
res.json({
message: 'post created.',
authData
});
}
});
res.json({
message: 'post created.'
});
});
app.post('/api/login', (req, res) => {
//mock user
const user = {
id: 1,
username: 'brad',
email: 'brad@gmail.com'
}
jwt.sign({ user }, 'secretkey', { expiresIn:'30s' }, (err, token) => {
res.json({ token });
});
});
//format of token
//authorization: bearer <access_token>
function verifyToken(req, res, next) {
//get auth header value
const bearerHeader = req.headers['authorization'];
//check if bearer is undefined
if (typeof bearerHeader !== 'undefined') {
//split at the space
const bearer = bearerHeader.split(' ');
//get token from array
const bearerToken = bearer[1];
//set the token
req.token = bearerToken;
//next middleware
next();
}
else {
//forbidden
res.sendStatus(403);
}
}
app.listen(5000, () => console.log('server started on port 5000'));
reference:
https://www.youtube.com/watch?v=7nafaH9SddU
https://jwt.io/introduction/
Subscribe to:
Posts (Atom)