Context for GraphQl resolvers undefined - javascript

I'm new to graphQL but for whatever reason is seems like my context parameter is undefined in my resolver. Here is how I am setting up the server:
index.js
import express from 'express';
import path from 'path';
import { fileLoader, mergeTypes, mergeResolvers } from 'merge-graphql-schemas';
import { ApolloServer } from 'apollo-server-express';
import models from './models';
const typeDefs = mergeTypes(fileLoader(path.join(__dirname, './schema')));
const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './resolvers')));
const PORT = 4000;
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.applyMiddleware({ app });
models.sequelize.sync({force: true}).then(x => {
app.listen({ port: PORT }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
});
Example Resolver:
export default {
Query: {
getUser: (parent, { id }, { models }) => models.User.findOne({ where: { id } }),
allUsers: (parent, args, { models }) => models.User.findAll(),
},
Mutation: {
createUser: (parent, args, { models }) => models.User.create(args),
},
};
When I try to do the createUser mutation I get the error: Cannot read property 'User' of undefined.
Which means my context object parameter is undefined. Any help is appreciated, GraphQL setup has been a bit confusing for me.

Related

How do I unit test an API mocking PrismaClient when the API is written in CommonJS?

I am testing a nodejs express API with a PG db wrapped with Prisma ORM
I configured a testing singleton instance as described by Prisma docs
Since the API is implemented in CommonJS and not TS, I had to make some changes as described in this beautiful page.
Here is a synthesis of what I did, will try to make it short, so it's easier to read
orgs.js (A GET route served by the mock server later on ...)
const Router = require('express-promise-router')
const router = new Router()
const PrismaPool = require('../db/PrismaPool');
module.exports = router
router.get('/assessments', async (req, res) => {
try{
const prisma = PrismaPool.getInstance();
const data = await prisma.org.findUnique({
select:{
assessments:true,
},
where: {
id: res.locals.orgId,
},
})
res.send(data)
}
catch(err){
handleError(err, "[GET]/orgs/assessments", 400, req, res)
}
})
PrismaPool.js (A wrapper to access the unique prisma client instance)
const prisma = require('./PrismaClientInstance').default
class PrismaPool {
constructor() {
throw new Error('Use PrismaPool.getInstance()');
}
static getInstance() {
return prisma
}
}
module.exports = PrismaPool;
PrismaClientInstance.js The unique instance of PrismaClient class. This is the tricky part stitching between the CommonJS world and the TS world.
'use strict';
exports.__esModule = true;
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
exports['default'] = prisma;
All this configuration works GREAT at runtime, now, when wrapping it with JEST in unit tests, things go south quickly ...
mock_server.js (a simplified server to expose the orgs API above)
const http = require('http');
const express = require('express');
var orgsRouter = require('../orgs');
const app = express();
app.use('/orgs', orgsRouter);
const port = 3011
app.set('port', port);
const server = http.createServer(app);
function onError(error) {
// herror handling
}
function onListening() {
// some debug messages
}
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
module.exports = server
PrismaSingletonForTesting.ts (A jest deep mock of the PrismaClient instance)
import { PrismaClient } from '#prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'
import prisma from './PrismaClientInstance'
jest.mock('./PrismaClientInstance', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}))
beforeEach(() => {
mockReset(prismaMock)
})
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
orgs.test.js (The tests of the orgs API)
const Request = require("request")
const { prismaMock } = require('../../db/PrismaSingletonForTesting')
const TEST_ORGID = 1
describe('egrest server', () => {
let server
beforeAll(() => {
server = require('./test_server')
})
afterAll(() => {
server.close()
})
describe('assessments', () => {
let data = {}
beforeAll(() => {
const testorg = {
id: TEST_ORGID,
name: 'jestest',
admin:33,
avail_tests: 1234
}
prismaMock.org.findUnique.mockResolvedValue(testorg)
})
it(`read remaining assessments for org ${TEST_ORGID}`, (done) => {
Request.get("http://localhost:3011/orgs/assessments", (error, response, body) => {
data.status = response.statusCode
data.body = body
data.error = error
console.dir(body)
done()
})
})
})
})
I also configured .jest.config with the required line setupFilesAfterEnv: ['./db/PrismaSingletonForTesting.ts']
When I run this test, I get data=undefined in orgs.js, even-though I mocked prisma.org.findUnique by doing prismaMock.org.findUnique.mockResolvedValue(testorg) as described by prisma docs.
Any help would be appreciated.

Backend with Express(JS) - GraphQL mutation function doesn't work

I don't get the request variable in the mutation GraphQL on the backend. I don't understand why it doesn't work.
I get the next error:
"Cannot destructure property 'name' of 'undefined' as it is undefined."
That mutation I make in Apollo Studio:
mutation Mutation($createOwnerName: String) {
createOwner(name: $createOwnerName)
}
My variables in Apollo Studio:
{
"createOwnerName": "John"
}
My backend with Express
schema.js:
const { buildSchema } = require("graphql");
const schema = buildSchema(`
type Mutation {
createOwner(name: String): String
}
`);
module.exports = schema;
resolvers.js:
const resolvers = {
Mutation: {
createOwner: ({name}) => {
console.log('createOwner name', name)
return name
}
}
}
server.js:
const { createServer } = require("http");
const express = require("express");
const { execute, subscribe } = require("graphql");
const { ApolloServer } = require("apollo-server-express");
const { SubscriptionServer } = require("subscriptions-transport-ws");
const { makeExecutableSchema } = require("#graphql-tools/schema");
const typeDefs = require("./graphql/schema.js");
const resolvers = require("./graphql/resolvers.js");
require("dotenv").config();
const mongoose = require("mongoose");
// mongoose
mongoose
.connect(process.env.DB_HOST, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("MongoDB connected"))
.catch((err) => console.log(err));
(async () => {
const PORT = 3033;
const app = express();
const httpServer = createServer(app);
app.get("/rest", function (req, res) {
return res.json({ data: "rest" });
});
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
});
await server.start();
server.applyMiddleware({ app });
SubscriptionServer.create(
{ schema, execute, subscribe },
{ server: httpServer, path: server.graphqlPath }
);
httpServer.listen(PORT, () => {
console.log(
`🚀 Query endpoint ready at http://localhost:${PORT}${server.graphqlPath}`
);
console.log(
`🚀 Subscription endpoint ready at ws://localhost:${PORT}${server.graphqlPath}`
);
});
})();
You are destructuring the wrong argument. Arguments are in that order:
Parent value
Argument values
Context
GraphQL Resolve Info
Destructure the second parameter:
const resolvers = {
Mutation: {
createOwner: (parent, {name}) => {
console.log('createOwner name', name)
return name
}
}
}

creating apollo-express-server with typescript and type-graphql error

I'm trying to set up an apollo server and an error shows up.
import { createConnection } from "typeorm";
import { Post } from "./entity/Post";
const express = require("express");
import { buildSchema } from "type-graphql";
import { ApolloServer } from "apollo-server-express";
import { PostResolver } from "./Resolvers/post";
createConnection()
.then(async (connection) => {
console.log("Inserting a new user into the database...");
const post = new Post();
post.firstName = "Timber";
post.lastName = "Saw";
post.age = 25;
await connection.manager.save(post);
console.log("Saved a new user with id: " + post.id);
console.log("Loading users from the database...");
const posts = await connection.manager.find(Post);
console.log("Loaded users: ", posts);
const app = express();
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => console.log(`Listening on PORT: ${PORT}`));
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [PostResolver],
}),
});
await apolloServer.start();
apolloServer.applyMiddleware({ app });
})
.catch((error) => console.log(error));
[ERROR] 16:49:21 ⨯ Unable to compile TypeScript:
src/index.ts:27:43 - error TS2345: Argument of type '{ schema: GraphQLSchema; }' is not assignable to parameter of type 'Config'.
Type '{ schema: GraphQLSchema; }' is missing the following properties from type 'Config': formatError, debug, rootValue, validationRules, and 6 more.
I've tried installing dependencies again, changed TypeScript config, restarting the whole project but nothing seems to fix the problem. Any clue?

How can I test express server with supertest in next.js?

I have built my portfolio webpage with next.js now I need to test it. to test the express server I use supertest. But the problem is I need to refactor express to use it. Because supertest need to access to app() before listening.
I started the way how I used to implement in node.js app. Put the express code in app.js and call it in index.js.
const express = require("express");
const server = express();
const authService = require("./services/auth");
const bodyParser = require("body-parser");
//put all the middlewares here
module.exports = server;
and then in index.js
const server = require("express")();
// const { parse } = require("url");
const next = require("next");
const routes = require("../routes");
const path = require("path");
require("./mongodb");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
// const handle = app.getRequestHandler(); //this is built in next route handler
const handle = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = require("./app");
//I required this outside too but it did not solve the issue
server.listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
with this set up, express is listening, I am able connect to mongodb, during the start up there is no issue.
When i request to localhost:3000, there is no response from localhost, it is spinning till timeout
Create a test client:
// test-client.ts
import { createServer, RequestListener } from "http";
import { NextApiHandler } from "next";
import { apiResolver } from "next/dist/next-server/server/api-utils";
import request from "supertest";
export const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
undefined,
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
Test your APIs with:
// user.test.ts
import viewerApiHandler from "../api/user";
import { testClient } from "../utils/test-client";
const request = testClient(viewerApiHandler);
describe("/user", () => {
it("should return current user", async () => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.body).toStrictEqual({ name: "Jane Doe" });
});
});
For those who want to add query parameters, here's the answer:
import { createServer, RequestListener } from 'http'
import { NextApiHandler } from 'next'
import { apiResolver } from 'next/dist/server/api-utils/node'
import request from 'supertest'
export const handlerRequester = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
let query = {}
let queryUrl = req.url.split('?')[1]
if (queryUrl) {
queryUrl
.split('&')
.map((p) => [p.split('=')[0], p.split('=')[1]])
.forEach((k) => {
query[k[0]] = k[1]
})
}
return apiResolver(
req,
res,
query,
handler,
{
previewModeEncryptionKey: '',
previewModeId: '',
previewModeSigningKey: '',
},
false
)
}
const server = createServer(listener)
return [request(server), server]
}
I've just released a new npm package which handle this case here:
https://www.npmjs.com/package/nextjs-http-supertest
Feel free to test it and give me feedback !

Getting user agent access in the resolver in graphql

I am relativity new to graphQL but this is annoying me. I want to get the user agent from the request body being sent from the client side. I can get access to the user-agent in the middleware however when I call the next function with any parameter to send to the resolver, I don't get any data from it. If I don't pass any parameters into next() then the resolver works as expected however parent, args, User and Session do not contain any information about the request headers. Any help or general tips would be greatly appreciated! Thanks!
app.js
import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import { graphiqlExpress, graphqlExpress } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';
import typeDefs from './Graphql/typeDefs';
import resolvers from './Graphql/resolver';
import { User } from './Mongoose/Schemas/user';
import { Session } from './Mongoose/Schemas/session';
mongoose.connect('mongodb://localhost/test');
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
const helperMiddleware = [
bodyParser.json(),
bodyParser.text({ type: 'application/graphql' }),
(req, res, next) => {
if ( req.body ) {
console.log(req.headers['user-agent']);
}
next();
},
];
const PORT = 3009;
const app = express();
app.use('/graphql', ...helperMiddleware, graphqlExpress({ schema, context: { User, Session } }));
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));
app.listen(PORT);
console.log(`Running On Port ${PORT}`);
resolver.js
Mutation: {
createUser: async (parent, args, { User, Session }) => {
const user = await new User(args).save();
user._id = user._id.toString();
const session = await new Session({
user_id: user._id,
userAgent: 'Nothing ATM',
ip: 'Nothing ATM',
}).save();
return user;
},
You need to use the callback version of creating the GraphQL server middleware, otherwise you have no way of constructing context based on the current request:
https://www.apollographql.com/docs/apollo-server/setup.html#options-function

Categories

Resources