I've been following all the results I found on Google on how to make an AuthDirective but I can't make it work.
I'll leave all the files to see if I'm making an error on the implementation but I guess not because I tested even without having any conditions inside the field.resolve.
One particular thing I found is that field.resolve, comes with 4 args, the first of them is always undefined, not sure if that could be it but just as an additional tip.
So here's the code:
This is a part of my typedefs
directive #isAuthenticated on FIELD_DEFINITION
scalar Date
type User {
id: ID,
name: String
email: String
password: String
createdAt: Date
}
type Token {
token: String
}
# IF I REMOVE #isAuthenticated this works!
type Query {
user(name: String): User! #isAuthenticated
users: [User!]! #isAuthenticated
}
type Mutation {
createUser(name: String, email: String, password: String): Boolean!
login(email: String!, password: String!): Token!
}
Here is my schema:
import path from 'path';
import { makeExecutableSchema } from 'graphql-tools';
import { fileLoader, mergeResolvers, mergeTypes } from 'merge-graphql-schemas';
import schemaDirectives from './Directives';
const typedefsArray = fileLoader(path.join(__dirname, './**/*.graphql'));
const resolversArray = fileLoader(path.join(__dirname, './**/*.resolvers.ts'));
const typeDefs = mergeTypes(typedefsArray);
const resolvers = mergeResolvers(resolversArray);
const schema = makeExecutableSchema({ typeDefs, resolvers, schemaDirectives });
export default schema;
Here is my AuthDirective
import { defaultFieldResolver } from 'graphql';
import { AuthenticationError, SchemaDirectiveVisitor } from 'apollo-server-express';
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field: any) {
const { resolver = defaultFieldResolver } = field;
field.resolve = async function (...args: any) {
const [_, __, context] = args;
const { req } = context;
const accessToken = req.headers.authorization
? req.headers.authorization.replace('Bearer ', '')
: null;
if (accessToken) {
// here I was doing jwt.verify(accessToken) but I removed it for now to simplify
const result = await resolver.apply(this, args);
return result; // <--- THIS IS ALWAYS UNDEFINED
} else {
throw new AuthenticationError('Unauthorized field');
}
};
}
}
export default AuthDirective;
which lives under ./Directives and is being exported from an index as:
import AuthDirective from './auth';
export default {
isAuthenticated: AuthDirective
}
Finally my user resolver. Like I said before, if I remove the directive from the user graphql query its working as it should:
import jwt from 'jsonwebtoken';
import { AuthenticationError, UserInputError } from 'apollo-server-express';
import { IUser } from './IUser';
import UserModel from './user.schema';
import { Config } from '../../config';
const { APP_SECRET } = Config;
const createToken = async (user: IUser, secret: string, expiresIn: string) => {
const { id, email, name } = user;
return jwt.sign({ id, email, name }, secret, { expiresIn });
};
const userResolvers = {
Query: {
user: (_: any, { name }: { name: string }) => UserModel.findOne({ name }).exec(),
users: () => UserModel.find({}).exec()
},
Mutation: {
createUser: async (_: any, args: IUser) => {
await new UserModel(args).save();
return true;
},
login: async (_: any, { email, password }: IUser) => {
const user = await UserModel.findOne({ email }).exec();
if (!user) {
throw new UserInputError('No user found with this login credentials.');
}
const isValid = await user.validatePassword(password);
if (!isValid) {
throw new AuthenticationError('Invalid password.');
}
return { token: createToken(user, APP_SECRET, '30m') };
}
}
};
export default userResolvers;
and my server:
import morgan from 'morgan';
import express from 'express';
import bodyParser from 'body-parser';
import compression from 'compression';
import depthLimit from 'graphql-depth-limit';
import { ApolloServer } from 'apollo-server-express';
import { Config, DBConfig } from './config';
import schema from './domain';
const { PORT } = Config;
const app = express();
const apolloServer = new ApolloServer({
schema,
validationRules: [depthLimit(7)],
context: ({ req, res }: any) => ({ req, res })
});
app.use(express.json());
app.use(compression());
app.use(morgan('combined'));
app.use(bodyParser.urlencoded({ extended: true }));
apolloServer.applyMiddleware({ app });
DBConfig.init();
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
There is no resolver property on field, so this
const { resolver = defaultFieldResolver } = field;
will always result in resolver having the value of defaultFieldResolver. If you're using the directive on some field that had a custom resolver, that custom resolver is never being called -- only the default behavior is used, which could indeed return undefined.
You need to make sure you're using the correct property:
const { resolve = defaultFieldResolver } = field;
or you can rename the resulting variable when you destructure field:
const { resolve: resolver = defaultFieldResolver } = field;
Related
Where am i going wrong here?
Using mocha, chai, sinon and proxyquire for an express server and sequelize ORM linked with a postgres database
I am trying to test a login controller route from my express server
Before I show the file which I want to run my test on here is what "../services/authService.js" file looks like
../services/authService
const UserService = require("./userService");
module.exports = class AuthService extends UserService {
};
// so UserService will have the method findByEmail
// UserService class looks like this and it is coming from another file require("./userService.js) as stated above
/*
class UserService {
async findByEmail(email) {
try {
const user = await User.findOne({ where: { email: email }});
if (user) {
return user;
}
throw new Error("User not found");
} catch (err) {
err.code = 404;
throw err
}
}
}
*/
And here is the auth-controller.js file which I want to run the test on
auth-controller.js
const bcrypt = require('bcryptjs');
const AuthService = require("../services/authService"); // is a class which extends from another calls see the code above
const authService = new AuthService();
const jwtGenerator = require('../utils/jwtGenerator');
const createError = require("http-errors");
exports.loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) throw createError(401, 'Incorrect password.');
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully 😊 👌", user, token });
} catch (error) {
next(error);
}
}
This code works in production but I cannot seem to test it. I used proxyquire to require the modules that the function uses. I have a big problem in making proxyquire work when it comes to my class AuthService here is my test file. As proxyquire is not working with classes some how. proxyquire is not using make AuthServiceMock at all cant figure out why.
First of these are my helper variables which I will use in the test file
../test-utils/user-helper
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let loginAdminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const loginUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const adminUser = {
id: 1,
email: 'admin#test.com',
password: '123456',
is_admin: true,
first_name: 'john',
last_name: 'doe',
created_at: "2020-06-26T09:31:36.630Z",
updated_at: "2020-06-26T09:31:49.627Z"
}
module.exports = {
createAccessToken,
loginDetail,
loginAdminUser,
loginUser,
adminUser
}
And here is the test file I placed comments espcially around proxyquire when I am trying to use it as this is giving me some issues when it comes to using it with classes. And as well it is not calling mocked/stubbed npm modules for some reason
auth-controller.spec.js
"use strict";
const _ = require("lodash");
const path = require("path");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const chai = require("chai");
const { expect } = chai;
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
// const AuthServiceOriginalClass = require("../../services/authService"); If i use this directly in proxyquire it calls the original class
const { createAccessToken, loginDetail, loginAdminUser, loginUser, adminUser } = require("../test-utils/user-helper");
const controllerPath = path.resolve('./controllers/authController.js');
describe("login route", () => {
let proxy, authService, bcryptStub, fakeCallback, fakeReq, fakeRes, fakeNext, resolveFn, token;
let result, bcryptStubbing, response;
class UserServiceMock {
async findByEmail(email) {
try {
if (email) {
return loginAdminUser;
}
} catch (error) {
throw error;
}
}
}
class AuthServiceMock extends UserServiceMock {};
bcryptStub = {
compare: function() { return true }
};
let tokeen = (kk) => {
return createAccessToken(kk);
}
// token = sinon.mock(createAccessToken(loginAdminUser)); // ?? which 1 to use?
token = sinon.spy(createAccessToken); // ?? which 1 to use?
// token = sinon.stub(createAccessToken) ?? which 1 to use?
proxy = proxyquire(controllerPath, {
"../services/authService.js": AuthServiceMock, // seems like this is not called at all
// "../services/authService.js": AuthServiceOriginalClass, // commented out if use this instead it calls the original class instant
"bcryptjs": bcryptStub,
"../utils/jwtGenerator": token,
// "#noCallThru": true // keep on or off?
});
before("Stub my methods", () => {
authService = new AuthServiceMock();
// If I call the entire loginRoute I want this stub authTry to be called inside of it and resolve that object value
authTry = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(loginAdminUser);
sinon.stub(bcryptStub, "compare").resolves(true); // force it to return true as that seems to be like the code of authController.js
// sinon.stub(token, "createAccessToken")
});
before("call the function loginRoute", async () => {
// fakeCallback = new Promise((res, rej) => {
// resolveFn = res
// });
fakeReq = {
body: {
email: loginDetail.email,
password: loginDetail.password
}
};
fakeRes = {
cookie: sinon.spy(),
status: sinon.spy(),
json: sinon.spy()
}
fakeNext = sinon.stub();
await proxy.loginRoute(fakeReq, fakeReq, fakeNext).then((_result) => {
result = _result;
});
console.log("result")
console.log(result) // undefined
console.log("result")
});
it("login route test if the stubs are called", async () => {
expect(authService.findByEmail).to.have.been.called // never called
// expect(bcryptStubbing).to.have.been.called // never called
// expect(response.status).to.deep.equal(200); // doesn't work
}).timeout(10000);
after(() => {
sinon.reset()
});
});
Where am i going wrong here in the test?
Since graphql-yoga V1 is no longer supported, I'd want to switch to graphql-yoga/node V2.
I've studied the official documentation on the website, but I'm having trouble migrating from V1 to V2.
Is a third-party package required?
here is a basic code:
const server = createServer({
schema: `type Query {
me: User!
posts(query: String): [Post!]!
users(query: String): [User!]!
comments(query: String): [Comment!]!
}`,
resolvers:{
Query: {
posts(parent, args, ctx, info) {
if (!args.query) {
return posts;
}
return posts.filter((post) => {
const isTitleMatch = post.title
.toLowerCase()
.includes(args.query.toLowerCase());
const isBodyMatch = post.body
.toLowerCase()
.includes(args.query.toLowerCase());
return isTitleMatch || isBodyMatch;
});
}
}
}
})
As you can see, I have resolvers and schema both are in single file named server.js
Could someone please assist me in this situation?
According to the docs it should be:
const server = createServer({
schema: {
typeDefs: `type Query {
me: User!
posts(query: String): [Post!]!
users(query: String): [User!]!
comments(query: String): [Comment!]!
}`,
resolvers: {
Query: {
posts(parent, args, ctx, info) {
if (!args.query) {
return posts;
}
return posts.filter((post) => {
const isTitleMatch = post.title
.toLowerCase()
.includes(args.query.toLowerCase());
const isBodyMatch = post.body
.toLowerCase()
.includes(args.query.toLowerCase());
return isTitleMatch || isBodyMatch;
});
}
}
}
}
})
anyway, here is an example for basic setup with typedefs and resolver in external files. note that it uses graphql-tools for uploading .graphql file for the schema, but you can easily use same method as resolvers for schema as .js file:
import { createServer } from '#graphql-yoga/node';
import { resolvers } from './resolvers.js';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { loadFiles } from '#graphql-tools/load-files';
const getSchema = async () =>
makeExecutableSchema({
typeDefs: await loadFiles('./*.graphql'),
resolvers,
});
async function main() {
const schema = await getSchema();
const server = createServer({ schema });
await server.start();
}
main();
I'm following this tutorial https://www.apollographql.com/docs/tutorial/mutation-resolvers/#book-trips
and have ended up getting this error:
"TypeError: Cannot read property 'findOrCreateUser' of undefined",
" at login
Below is the function I use to call login:
mutation LoginUser {
login(email: "daisy#apollographql.com") {
token
}
}
The file below is where findOrCreateUser is called:
src/resolvers.js
module.exports = {
Mutation: {
login: async (_, {email}, {dataSources}) => {
const user = await dataSources.userAPI.findOrCreateUser({ email });
if (user) {
user.token = Buffer.from(email).toString('base64');
return user;
}
},
},
This is where dataSources is defined:
src/index.js
require('dotenv').config();
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const { createStore } = require('./utils');
const resolvers = require('./resolvers');
const isEmail = require('isemail');
const LaunchAPI = require('./datasources/launch');
const UserAPI = require('./datasources/user');
const store = createStore();
const server = new ApolloServer({
context: async ({req}) => {
const auth = req.headers && req.headers.authorization || '';
const email = Buffer.from(auth, 'base64').toString('ascii');
if (!isEmail.validate(email)) return {user: null};
// find a user by their email
const users = await store.user.findOrCreate({ where: { email } });
const user = users && users[0] || null;
return { user: {...user.dataValues } };
},
dataSources: () => ({
launchAPI: new LaunchAPI(),
UserAPI: new UserAPI({store})
}),
typeDefs,
resolvers,
});
server.listen().then(() => {
console.log(`
Server is running!
Listening on port 4000
Explore at https://studio.apollographql.com/dev
`)
});
As xadm mentioned in the comment the issues is your usage of userAPI vs UserAPI. the datasource object is created in index.js in the following block
dataSources: () => ({
launchAPI: new LaunchAPI(),
UserAPI: new UserAPI({store})
}),
here you defined datasources.UserAPI however in resolvers.js you refer to it as datasources.userAPI (note the difference in capitalization of userAPI). Your problem can be resolved by changing the above code block to
dataSources: () => ({
launchAPI: new LaunchAPI(),
userAPI: new UserAPI({store})
}),
Hope you could help me out with the following. I am trying to upload an excel file of ≈3MB from the client side to the API by first converting the file to a DataURL where after I send it as a string. This is working for smaller files, but it somehow seems to be blocking my larger files.
When I upload the file, I get the following error.
POST body missing. Did you forget use body-parser middleware?
I have done my own research and found more people with the same problem, though I could not find a solution.
https://github.com/apollographql/apollo-server/issues/792
This is the code I am using on the server side.
import { ApolloServer, gql } from 'apollo-server-micro'
type Props = {
_id: string
file: string[]
}
const typeDefs = gql`
type Mutation {
uploadFile(file: [String!]!): Boolean!
}
type Query {
readUpload(_id: String!): Boolean!
}
`
const resolvers = {
Mutation: {
async uploadFile(_: any, { file }: Props) {
console.log(file)
return true
}
},
Query: {
async readUpload(_: any, { _id }: Props) {
}
}
}
const apolloServer = new ApolloServer({
typeDefs,
resolvers
})
export const config = {
api: {
bodyParser: false
}
}
// Ensure to put a slash as the first character to prevent errors.
export default apolloServer.createHandler({ path: '/api/uploads' })
This is the code I am using on the client side.
import { useRef } from 'react'
import { uploadFile } from '../graphql/fetchers/uploads'
import { UPLOAD_FILE_QUERY } from '../graphql/queries/uploads'
export default function Upload() {
const inputElement = useRef<HTMLInputElement>(null)
const submitForm = (event: any) => {
event.preventDefault()
const files = inputElement.current?.files
if (files) {
const fileReader = new FileReader()
fileReader.onload = async () => {
try {
const result = fileReader.result as string
try {
console.log(result)
await uploadFile(UPLOAD_FILE_QUERY, { file: result })
} catch(error) {
console.log(error)
}
} catch(error) {
console.log(error)
}
}
fileReader.readAsDataURL(files[0])
}
}
return (
<form>
<input ref={inputElement} type='file'></input>
<button onClick={(event) => submitForm(event)}>Submit</button>
</form>
)
}
export const config = {
api: {
bodyParser: false
}
}
set bodyParser to true
Set bodyParser size limit
export const config = {
api: {
bodyParser: {
sizeLimit: '4mb' // Set desired value here
}
}
}
you try to send file as string in json? I think you should use multipart/form data on client side and parse them with special middleware on server side
On client special link converts request to multipart/formdata
full example https://github.com/jaydenseric/apollo-upload-examples
import { useMemo } from "react"
import { ApolloClient, createHttpLink, InMemoryCache } from "#apollo/client"
import { setContext } from "#apollo/client/link/context"
import { getUserTokenFromLocalStorage } from "../utils/utils"
import { createUploadLink } from "apollo-upload-client"
let apolloClient
const httpLink = createUploadLink({
uri: "/api/graphql",
headers: {
"keep-alive": "true",
},
})
const authLink = setContext((_, { headers }) => {
let token = getUserTokenFromLocalStorage()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
}
})
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema")
const { schema } = require("./schema")
return new SchemaLink({ schema })
} else {
return authLink.concat(httpLink)
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
It works just once for the below code
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
buildSchema,
} from "https://cdn.pika.dev/graphql/^15.0.0";
import { serve } from "https://deno.land/std#0.50.0/http/server.ts";
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
hello: {
type: GraphQLString,
resolve() {
return "world";
},
},
},
}),
});
var query = "{ hello }";
graphql(schema, query).then((result) => {
console.log(result);
});
How to keep it listening, just like express
Something like this
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
type Query {
hello: String
}
`);
// The root provides a resolver function for each API endpoint
var root = {
hello: () => {
return 'Hello world!';
},
};
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
import {
graphql,
buildSchema,
} from "https://cdn.pika.dev/graphql/^15.0.0";
import {Application, Router} from "https://deno.land/x/oak/mod.ts";
var schema = buildSchema(`
type Query {
hello: String
}
`);
var resolver = {hello: () => 'Hello world!'}
const executeSchema = async (query:any) => {
const result = await graphql(schema, query, resolver);
return result;
}
var router = new Router();
router.post("/graph", async ({request, response}) => {
if(request.hasBody) {
const body = await request.body();
const result = await executeSchema(body.value);
response.body = result;
} else {
response.body = "Query Unknown";
}
})
let app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
console.log("Server running");
app.listen({port: 5000})
You can now use https://deno.land/x/deno_graphql to achieve this goal.
It provides everything needed out-of-the-box and works with multiple Deno frameworks (oak, abc, attain, etc).
This is how you code looks like (with oak for example):
import { Application, Context, Router } from "https://deno.land/x/oak/mod.ts";
import {
gql,
graphqlHttp,
makeExecutableSchema,
} from "https://deno.land/x/deno_graphql/oak.ts";
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => "Hello world!",
},
};
const context = (context: Context) => ({
request: context.request,
});
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = new Application();
const router = new Router();
router.post("/graphql", graphqlHttp({ schema, context }));
app.use(router.routes());
await app.listen({ port: 4000 });
PS : i'm the author of the package, so you can ask me anything.
Hope this helps!
Here is an example using oak working with your GraphQL code.
First let's say you have a repository graphRepository.ts with your graph schema:
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString
} from "https://cdn.pika.dev/graphql/^15.0.0";
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
hello: {
type: GraphQLString,
resolve() {
return "world";
},
},
},
}),
});
export async function querySchema(query: any) {
return await graphql(schema, query)
.then(async (result) => {
return result;
});
}
Now start your app.ts listener with the routes, and use the following URL to call the endpoint:
http://localhost:8000/graph/query/hello
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import { querySchema } from "./graphRepository.ts";
const router = new Router();
router
.get("/graph/query/:value", async (context) => {
const queryValue: any = context.params.value;
const query = `{ ${queryValue}}`
const result = await querySchema(query);
console.log(result)
context.response.body = result;
})
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
here is a code example using oak and middleware.
You also can enjoy the playground GUI like an apollo one.
import { Application } from "https://deno.land/x/oak/mod.ts";
import { applyGraphQL, gql } from "https://deno.land/x/oak_graphql/mod.ts";
const app = new Application();
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
const types = gql`
type User {
firstName: String
lastName: String
}
input UserInput {
firstName: String
lastName: String
}
type ResolveType {
done: Boolean
}
type Query {
getUser(id: String): User
}
type Mutation {
setUser(input: UserInput!): ResolveType!
}
`;
const resolvers = {
Query: {
getUser: (parent: any, {id}: any, context: any, info: any) => {
console.log("id", id, context);
return {
firstName: "wooseok",
lastName: "lee",
};
},
},
Mutation: {
setUser: (parent: any, {firstName, lastName}: any, context: any, info: any) => {
console.log("input:", firstName, lastName);
return {
done: true,
};
},
},
};
const GraphQLService = applyGraphQL({
typeDefs: types,
resolvers: resolvers
})
app.use(GraphQLService.routes(), GraphQLService.allowedMethods());
console.log("Server start at http://localhost:8080");
await app.listen({ port: 8080 });
I have created gql for making GraphQL servers that aren't tied to a web framework. All of the responses above show Oak integration but you don't really have to use it to have a GraphQL server. You can go with std/http instead:
import { serve } from 'https://deno.land/std#0.90.0/http/server.ts'
import { GraphQLHTTP } from 'https://deno.land/x/gql/mod.ts'
import { makeExecutableSchema } from 'https://deno.land/x/graphql_tools/mod.ts'
import { gql } from 'https://deno.land/x/graphql_tag/mod.ts'
const typeDefs = gql`
type Query {
hello: String
}
`
const resolvers = {
Query: {
hello: () => `Hello World!`
}
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
const s = serve({ port: 3000 })
for await (const req of s) {
req.url.startsWith('/graphql')
? await GraphQLHTTP({
schema,
graphiql: true
})(req)
: req.respond({
status: 404
})
}