@zeusdeux/serverless-graphql
v0.0.16
Published
Run GraphQL from anywhere without the need for server
Downloads
30
Maintainers
Readme
serverless-graphql
Run GraphQL on any schema directly from anywhere without the need for a graphql server.
Supports Query
, Mutation
and Subscription
types.
This is super experimental.
Installation
npm install @zeusdeux/serverless-graphql
You can use other package managers as well.
API
makeExecutableSchema
This function accepts an options
object with two properties for now and returns a GraphQLSchema
.
Return type
GraphQLSchema
e.g.,
import { makeExecutableSchema } from '@zeusdeux/serverless-graphql'
const typeDefs = gql`
type Query {
hello: String!
}
`
const resolvers = {
Query: {
hello: () => 'world!'
}
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
// this schema can then be consumed by other packages
// such as apollo-server, etc to run a dev server for example.
options.typeDefs
These are the type definitions of your GraphQL server.
e.g.,
type Query {
authors: [Author]!
author(name: String!): Author!
books: [Book]!
}
type Author {
name: String!
age: Int!
addedAt: String!
books: [Book]!
}
type Book {
name: String!
publisher: String!
publishedYear: Int!
authors: [Author!]
}
input AuthorInput {
name: String!
age: Int!
}
input BookInput {
name: String!
publisher: String!
publishedYear: Int!
}
type Mutation {
addAuthor(author: AuthorInput!): Author!
addBookForAuthor(authorName: String!, book: BookInput): Book!
}
options.resolvers
This an object contain the resolvers for the types provided in the typeDefs
. If any resolver is
missing, the
default resolver is
used.
The resolver function can also be async
and use the async/await
syntax.
If a resolver throws an error, the query execution is aborted and the error is returned to the user
in the errors
key of the response.
A working example matching the schema above is provided below.
const AuthorDB = {}
const BookDB = {}
const delay = ms => {
return new Promise(res => {
setTimeout(res, ms)
})
}
const resolvers = {
Query: {
authors() {
return Object.values(AuthorDB)
},
author(_, { name }) {
if (name in AuthorDB) {
return AuthorDB[name]
}
throw new Error(`No author named ${name} found in database`)
},
books() {
return Object.values(BookDB)
}
},
// Author: {}, // not providing this lets us use the default resolvers
Mutation: {
addAuthor(_, { author }) {
const { name, age } = author
// add author if it doesn't exist in DB
if (!(name in AuthorDB)) {
AuthorDB[name] = {
name,
age,
addedAt: new Date().toISOString(),
books: []
}
}
return AuthorDB[name]
},
async addBookForAuthor(_, { authorName, book: { name: bookName, publisher, publishedYear } }) {
await delay(2500)
if (authorName in AuthorDB) {
const author = AuthorDB[authorName]
const authorHasBook = !!author.books.filter(book => book.name === bookName).length
const bookIdx = `${bookName}:${publisher}:${publishedYear}`
const book = BookDB[bookIdx]
if (authorHasBook) {
return book
}
if (book) {
book.authors.push(author)
BookDB[bookIdx] = book
} else {
BookDB[bookIdx] = {
name: bookName,
publisher,
publishedYear,
authors: [author]
}
}
author.books.push(BookDB[bookIdx])
return BookDB[bookIdx]
}
throw new Error(`Author with name ${name} not found`)
}
}
}
getQueryRunner
This function accepts the same options as makeExecutableSchema
or an
object containing a GraphQLSchema
i.e., { schema: GraphQLSchema }
. It returns an object with two
properties, graphql
and subscribe
.
Return type
type GetQueryRunnerResult<T = { [key: string]: any }> = {
graphql: (request: string | QueryRunnerOptions) => Promise<ExecutionResult<T>>
subscribe: (request: string | QueryRunnerOptions) => Promise<AsyncIterable<ExecutionResult<T>>>
}
type QueryRunnerOptions = {
req: string
variables?: { [key: string]: any }
root?: any
context?: any
operationToRun?: string
}
type ExecutionResult<T> = {
errors?: ReadonlyArray<GraphQLError>
data?: T | null
}
Both functions returned by getQueryRunner
can be provided with the operation as a string or as a
string assigned to req
property of an object. The object also supports variables
which let you
use graphql variables, context
which is passed to all resolvers, root
value which gets passed as
the root value to the executor and operationToRun
which can be used to select which operation to
run if many are provided in the request (req
) string.
The functions on the returned object are explained below.
graphql
This function accepts a GraphQL request to execute on the given schema or schema generated from
{ typeDefs, resolvers }
(i.e., the args to getQueryRunner
). It should only be used for queries
and mutations.
It returns a Promise
which resolves to an object containing data
and errors
properties, one
of which is usually populated. data
maps to the resolved response. errors
contains errors that
occured during execution. This maps to the ExecutionResult
type shown above.
e.g.,
import { makeExecutableSchema, getQueryRunner, gql } from '@zeusdeux/serverless-graphql'
const typeDefs = gql`
type Query {
hello: String!
}
`
const resolvers = {
Query: {
hello: () => 'world!'
}
}
const { graphql: runQuery } = getQueryRunner({ typeDefs, resolvers })
runQuery('{ hello }').then(({ data }) => console.log(data)) // logs {hello: "world!"}
// or
const schema = makeExecutableSchema({ typeDefs, resolvers })
const { graphql: runQuery } = getQueryRunner({ schema })
runQuery('{ hello }').then(({ data }) => console.log(data)) // logs {hello: "world!"}
An example with the variables
option being used
import { getQueryRunner, gql } from '@zeusdeux/serverless-graphql'
const typeDefs = gql`
type Query {
hello: String!
}
type Mutation {
getTime(iso: Boolean = false): String
}
`
const resolvers = {
Query: {
hello: () => 'world!'
},
Mutation: {
getTime(_, { iso }) {
const t = new Date()
return iso ? t.toISOString() : t.getTime()
}
}
}
const { graphql: runQuery } = getQueryRunner({ typeDefs, resolvers })
runQuery({
req: gql`
mutation whatTimeIsIt($iso: Boolean) {
currentServerTime: getTime(iso: $iso)
}
`,
variables: {
iso: true
}
}).then(({ data }) => console.log(data)) // logs server time in ISO format
subscribe
This function accepts a GraphQL subscription
request to execute on the given schema or schema
generated from { typeDefs, resolvers }
(i.e., the args to getQueryRunner
). It should only be
used for subscription
requests.
It returns a Promise
that resolves to an
async iterable
that can be
consumed using the new
for await...of
syntax for example.
The values generated by the async iterable have the same shape and type as those returned by the
graphql
function mentioned above. I.e., Promise
s that resolve to { data?, errors? }
aka
ExecutionResult
.
e.g.,
import { getQueryRunner, gql } from '@zeusdeux/serverless-graphql'
const delay = ms => {
return new Promise(res => {
setTimeout(res, ms)
})
}
const typeDefs = gql`
type Subscription {
getNumber: Int!
}
`
const resolvers = {
Subscription: {
getNumber: {
subscribe: () => generateNumbers()
}
}
}
async function* generateNumbers() {
let x = 1
while (x < 5) {
// synthetic delay
await delay(1000)
// whatever is yielded is passed as the root value
// to the resolve function next to the subscribe.
// In this example, there isn't one which means
// GraphQL uses the default resolver which in
// this case is resolve(root) = root.getNumber
yield {
getNumber: x++
}
}
}
async function main() {
const { subscribe } = getQueryRunner({ typeDefs, resolvers })
for await (let x of await subscribe('subscription { getNumber }')) {
console.log('Number:', x) // prints Number: 1, Number: 2, and so on
}
}
main().catch(err => console.log.bind('😱', err))
gql
An alias for String.raw
. This lets prettier
format the type definitions for you automatically.
Packages published
This repo publishes a umd package with its external dependencies, graphql
and graphql/utilities
,
all bundled up together as a UMD bundle for ease of usage from a browser.
For usage from an application, it does not bundle up any of its external dependencies (graphql
and graphql/utilities
). It instead marks graphql
(which includes graphql/utilities
) as a peer
dependency under peerDependencies
. It is up to the consumer of this package to install the
peerDependencies
in their application. This is because, at any given time, only one instance of
graphql
can be installed otherwise it starts throwing when it's functions are consumed.
UNPKG
UNPKG serves umd/serverless-gql.umd.js
which is minified and contains the
dependencies within.
CJS
Usage of this package using require
or in CJS environments consumes dist/serverless-gql.cjs.js
.
ESM
In the context of an ESM based codebase, bundlers can choose to consume dist/serverless-gql.esm.js
which is a build with import
/export
statements kept intact for better tree-shaking to enable
dead code elimination.
Development
- Fork (if not the author) and clone the repository
cd
in the directory and runnpm i
npm install
thepeerDependencies
of this package if you will be building the code (to publish, test, etc)- Make your changes as required on a well named branch
- Open a PR against the origin repository