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();
Related
GraphQL returing hello type as NULL but everything is correct. What could me the possible reasons for this as everything is correct ?
const { ApolloServer, gql } = require("apollo-server")
const typeDefs = gql`
type Query {
hello: String
}
`
const Resolvers = {
Query: {
hello: () => {
return "World!"
},
}
}
const server = new ApolloServer({
typeDefs,
Resolvers
});
server.listen().then(({ url }) => {
console.log('Server is ready' + url);
})
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
})
}
This is rather a stylistic question. I'm using Pino in some of my Javascript/Typescript microservices. As they're running on AWS I'd like to propagate the RequestId.
When one of my functions is invoked, I'm creating a new child logger like this:
const parentLogger = pino(pinoDefaultConfig)
function createLogger(context) {
return parentLogger.child({
...context,
})
}
function createLoggerForAwsLambda(awsContext) {
const context = {
requestId: awsContext.awsRequestId,
}
return createLogger(context)
}
I'm then passing down the logger instance to all methods. That said, (... , logger) is in almost every method signature which is not too nice. Moreover, I need to provide a logger in my tests.
How do you do it? Is there a better way?
you should implement some sort of Dependency Injection and include your logger there.
if your using microservices and maybe write lambdas in a functional approach, you can handle it by separating the initialization responsibility in a fashion like this:
import { SomeAwsEvent } from 'aws-lambda';
import pino from 'pino';
const createLogger = (event: SomeAwsEvent) => {
return pino().child({
requestId: event.requestContext.requestId
})
}
const SomeUtil = (logger: pinno.Logger) => () => {
logger.info('SomeUtil: said "hi"');
}
const init(event: SomeAwsEvent) => {
const logger = createLogger(event);
someUtil = SomeUtil(logger);
return {
logger,
someUtil
}
}
export const handler = (event: SomeAwsEvent) => {
const { someUtil } = init(event);
someUtil();
...
}
The simplest way is to use some DI library helper to tackle this
import { createContainer } from "iti"
interface Logger {
info: (msg: string) => void
}
class ConsoleLogger implements Logger {
info(msg: string): void {
console.log("[Console]:", msg)
}
}
class PinoLogger implements Logger {
info(msg: string): void {
console.log("[Pino]:", msg)
}
}
interface UserData {
name: string
}
class AuthService {
async getUserData(): Promise<UserData> {
return { name: "Big Lebowski" }
}
}
class User {
constructor(private data: UserData) {}
name = () => this.data.name
}
class PaymentService {
constructor(private readonly logger: Logger, private readonly user: User) {}
sendMoney() {
this.logger.info(`Sending monery to the: ${this.user.name()} `)
return true
}
}
export async function runMyApp() {
const root = createContainer()
.add({
logger: () =>
process.env.NODE_ENV === "production"
? new PinoLogger()
: new ConsoleLogger(),
})
.add({ auth: new AuthService() })
.add((ctx) => ({
user: async () => new User(await ctx.auth.getUserData()),
}))
.add((ctx) => ({
paymentService: async () =>
new PaymentService(ctx.logger, await ctx.user),
}))
const ps = await root.items.paymentService
ps.sendMoney()
}
console.log(" ---- My App START \n\n")
runMyApp().then(() => {
console.log("\n\n ---- My App END")
})
it is easy to write tests too:
import { instance, mock, reset, resetCalls, verify, when } from "ts-mockito"
import { PaymentService } from "./payment-service"
import type { Logger } from "./logger"
const mockedLogger = mock<Logger>()
when(mockedLogger.info).thenReturn(() => null)
describe("Payment service: ", () => {
beforeEach(() => {
resetCalls(mockedLogger)
// reset(mockedLogger)
})
it("should call logger info when sending money", () => {
const paymentService = new PaymentService(instance(mockedLogger))
expect(paymentService.sendMoney()).toBe(true)
})
})
I would not use the requestId as part of the context of the logger, but use it as the payload of the logger, like logger.info({ requestId }, myLogMessage). This was you can have a simple function create a child logger that you can use for the entire module.
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;