I need Apollo GraphQL to send back http responses - graphql

I'd like for the ability to have my express graphQL server to send back the HTTP status code along with the response to the client. At the moment, all I see is data, however, additional functionality based on the status code would be helpful.
I don't see anything here, unless I'm looking in the wrong place:
http://dev.apollodata.com/tools/graphql-server/setup.html

For Apollo Server 2.2.x and later, we can use plugins to custom the HTTP status code. Custom the HTTP status code in willsendresponse event handler. You can check the errors array and set the corresponding HTTP status code for different type of error.
E.g.
server.ts:
import { ApolloServer, gql } from 'apollo-server';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
function customHTTPStatusPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
return {
willSendResponse({ errors, response }) {
if (response && response.http) {
if (errors) {
response.data = undefined;
response.http.status = 500;
}
}
},
};
},
};
}
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello() {
throw new Error('something happened');
},
},
};
const server = new ApolloServer({ typeDefs, resolvers, plugins: [customHTTPStatusPlugin()] });
const port = 3000;
server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`));
The response:
{
"error": {
"errors": [
{
"message": "something happened",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"hello"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: something happened",
" at hello (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/src/stackoverflow/40387508/server.ts:30:13)",
" at field.resolve (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/apollo-server-express/node_modules/graphql-extensions/src/index.ts:274:18)",
" at field.resolve (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/apollo-server-express/node_modules/apollo-server-core/src/utils/schemaInstrumentation.ts:103:18)",
" at resolveFieldValueOrError (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/graphql/execution/execute.js:467:18)",
" at resolveField (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/graphql/execution/execute.js:434:16)",
" at executeFields (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/graphql/execution/execute.js:275:18)",
" at executeOperation (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/graphql/execution/execute.js:219:122)",
" at executeImpl (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/graphql/execution/execute.js:104:14)",
" at Object.execute (/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/graphql/execution/execute.js:64:35)",
" at /Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/node_modules/apollo-server-express/node_modules/apollo-server-core/src/requestPipeline.ts:548:22"
]
}
}
}
]
}
}
You will also get the HTTP status code - 500 Internal Server Error at the client-side.

Related

Remove null results from a array that can contain nullable values in GraphQL

I have a query in my app that works but response is little ugly, there is probably two ways to solve this:
Write resolver differently
Clean response from null values
Here is resolver:
t.list.field('manyDevices', {
type: 'Device',
description: 'Get list of devices belonging to user',
args: {
input: nonNull(deviceIdentifierInput.asArg()),
},
resolve: async (_, { input: { id } }, { prisma }) => {
return await prisma.device.findMany({ where: { userId: id } });
},
});
This resolver looks for all devices with provided id. Id can be mine and also can be from a some other user. Devices can be public and private, and I don't want to receive private devices except if they are mine.
const isDevicePublic = rule({ cache: 'strict' })(
async ({ isPublic }: Device) => {
if (!isPublic) {
return permissionErrors.noPermission;
}
return true;
},
);
const isDeviceOwner = rule({ cache: 'strict' })(
async ({ userId }: Device, _, { user }: Context) => {
assertValue(user, permissionErrors.noAuthentication);
if (userId !== user.id) {
return permissionErrors.noPermission;
}
return true;
},
);
These are rules that I place on my schema with graphql-shield library and it works. There is just one problem, if a user have a private device it will be listed in response array as null and graphql-shield will throw error, so response can look like this:
{
"errors": [
{
"message": "You have no permission to access this resource",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"manyDevices",
0,
"name"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: You have no permission to access this resource",
" at Rule.resolve (/workspace/node_modules/graphql-shield/dist/rules.js:33:24)",
" at runMicrotasks (<anonymous>)",
" at processTicksAndRejections (internal/process/task_queues.js:93:5)",
" at async Promise.all (index 0)"
]
}
}
}
],
"data": {
"manyDevices": [
null,
{
"name": "device-01"
}
]
}
}
So there is one fetched device and other that is private that throws this error, can I somehow remove null and error response or should I filter them out in resolver?

Followed GraphQL tutorial / Resolver object to query MySQL DB fails

Developing entirely local, I followed the tutorial here: https://scotch.io/tutorials/super-simple-graphql-with-node#toc-creating-the-graphql-server
Hardly any modification, only to restrict the focus to a "product" table. No "User" or "Recipe" table.
It appears the only issue I have now is retrieving the data from the MySQL DB.
Of course I've Googled and compared tutorials extensively. I've been through the code quite a bit as well to the point it's getting blurry.
If I change the "return" to {"name":"test"}, the data passes through to my local GraphQL dashboard perfectly.
models/index.js
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs.readdirSync(__dirname).filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
}).forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
}); //console.log(db); Object.keys(db).forEach(modelName => { if (db[modelName].associate) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize;
module.exports = db;
'use strict';
module.exports = (sequelize, DataTypes) => {
const Product = sequelize.define('Product', {
name: DataTypes.STRING,
urlWordRef: DataTypes.STRING,
seoTitle: DataTypes.STRING,
seoDescription: DataTypes.STRING,
standardEquipment: DataTypes.STRING,
technicalSpecs: DataTypes.STRING
}, {});
Product.associate = function(models) {
// associations can be defined here
//Product.hasMany(models.RefProductOptions)
};
return Product;
};
// src/resolvers.js
const resolvers = {
Query: {
async products (root, { id }, { models }) {
return models.Products.findById(id) // *** THE LINE OF CODE WHICH FAILS ***
return {"name":"test"} // ** SUBSTITUTING THE ABOVE LINE WITH THIS - SUCCESS! ***
}
}
}
module.exports = resolvers
// src/schema.js
const { gql } = require('apollo-server')
const typeDefs = gql `
type Products {
id: Int!
name: String
urlWordRef: String
seoTitle: String
seoDescription: String
standardEquipment: String
technicalSpecs: String
}
type Query {
products(id: Int!): Products
}
`;
module.exports = typeDefs
// src/index.js
const { ApolloServer } = require('apollo-server')
const typeDefs = require('./schema')
const resolvers = require('./resolvers')
const models = require('../models')
const server = new ApolloServer({
typeDefs,
resolvers,
context: { models }
})
server.listen({port: 4000}).then(({ url }) => console.log('Server is running on localhost:4000'))
GraphQL query via the dashboard:
{
products(id: 1) {
name
}
}
I would expect that I can pass the id via the noted GRaphQL query above and get the correct table row result.
Alas, I'm ready to start coding with my forehead!
Error response in GraphQL:
{
"errors": [
{
"message": "Cannot read property 'findById' of undefined",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"products"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read property 'findById' of undefined",
" at products (/home/bob/graphql-server/src/resolvers.js:6:26)",
" at field.resolve (/home/bob/graphql-server/node_modules/graphql-extensions/dist/index.js:140:26)",
" at resolveFieldValueOrError (/home/bob/graphql-server/node_modules/graphql/execution/execute.js:486:18)",
" at resolveField (/home/bob/graphql-server/node_modules/graphql/execution/execute.js:453:16)",
" at executeFields (/home/bob/graphql-server/node_modules/graphql/execution/execute.js:294:18)",
" at executeOperation (/home/bob/graphql-server/node_modules/graphql/execution/execute.js:238:122)",
" at executeImpl (/home/bob/graphql-server/node_modules/graphql/execution/execute.js:85:14)",
" at Object.execute (/home/bob/graphql-server/node_modules/graphql/execution/execute.js:62:35)",
" at /home/bob/graphql-server/node_modules/apollo-server-core/dist/requestPipeline.js:239:46",
" at Generator.next (<anonymous>)",
" at /home/bob/graphql-server/node_modules/apollo-server-core/dist/requestPipeline.js:7:71",
" at new Promise (<anonymous>)",
" at __awaiter (/home/bob/graphql-server/node_modules/apollo-server-core/dist/requestPipeline.js:3:12)",
" at execute (/home/bob/graphql-server/node_modules/apollo-server-core/dist/requestPipeline.js:218:20)",
" at Object.<anonymous> (/home/bob/graphql-server/node_modules/apollo-server-core/dist/requestPipeline.js:156:42)",
" at Generator.next (<anonymous>)",
" at fulfilled (/home/bob/graphql-server/node_modules/apollo-server-core/dist/requestPipeline.js:4:58)",
" at <anonymous>",
" at process._tickCallback (internal/process/next_tick.js:188:7)"
]
}
}
}
],
"data": {
"products": null
}
}

Getting "Cannot return null" error while querying API using Graphql? [duplicate]

This question already has answers here:
Why does a GraphQL query return null?
(6 answers)
Closed 3 years ago.
I am not sure what I am doing wrong. I cant query this that seems to be a very easy API using GraphQL?
https://data.brreg.no/enhetsregisteret/enhet.json
I have tried different solutions. But I get the same error/problem.
import { ApolloServer, gql, IResolverObject } from "apollo-server";
import fetch from "node-fetch";
const typeDefs = gql`
type Company {
organisasjonsnummer: Int
navn: String
}
type Query {
companies: [Company!]!
}
`;
const resolvers: IResolverObject = {
Query: {
companies: async () => {
const response = await fetch(
"https://data.brreg.no/enhetsregisteret/enhet.json"
);
const data = await response.json();
return data.results;
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Query used in GraphQL Playground:
{
companies{
organisasjonsnummer
}
}
Error received from GraphQL Playground:
{
"errors": [
{
"message": "Cannot return null for non-nullable field Query.companies.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"companies"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field Query.companies.",
" at completeValue (F:\\apollo-datasource-rest-example\\node_modules\\graphql\\execution\\execute.js:573:13)",
" at F:\\apollo-datasource-rest-example\\node_modules\\graphql\\execution\\execute.js:505:16",
" at process._tickCallback (internal/process/next_tick.js:68:7)"
]
}
}
}
],
"data": null
}
Because the JSON return from that link does not have a field called "results" , so null is resolved for the companies root query but it is expected to have a non-nullable value because its schema is defined as [Company!]!.
You should return data.data from this resolver based on the JSON structure return from that link.

how to return customize data in prisma subscription

I am learning graphql & prisma and I came across a question on prisma subscription.
I want to return an item list whenever there is a creation or update on Item. So this is my code which not works.
scheme.graphql
# import Item from "./generated/prisma.graphql"
type Subscription {
todoItems: TodoItems
}
type TodoItems {
items: [Item!]!
}
resolver
const Subscription = {
todoItems: {
subscribe: async (parent, args, context, info) => {
const itemSubscription = await context.db.subscription.item({
where: { mutation_in: ['CREATED', 'UPDATED'] },
}, info);
return itemSubscription;
},
resolve: async (payload, args, context, info) => {
const items = await context.db.query.items({ type: 0, orderBy: 'updatedAt_DESC' }, info);
return { items };
},
},
}
module.exports = {
Subscription,
}
and in graphql playground,
subscription{
todoItems{
items{
title
}
}
}
it gives the error:
{
"errors": [
{
"message": "Anonymous Subscription must select only one top level field.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"todoItems"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Anonymous Subscription must select only one top level field.",
" at asErrorInstance (d:\\git\\inote\\node_modules\\graphql\\execution\\execute.js:489:43)",
" at <anonymous>",
" at process._tickCallback (internal/process/next_tick.js:118:7)"
]
}
}
}
]
}
Any idea?
Prisma does not support subscribing item lists. Instead, prisma wants you to subscribe to single item mutations ("created", "updated", "deleted"). As described here.
E.g.
subscription newTodos {
todo(where: {
mutation_in: [CREATED]
}) {
mutation
node {
title
}
}
}
To get "the full list", you have to query on the todos after subscribing to avoid missing events (race condition). As a result you have to manually "sync" the data from the subscription and your query.

GraphQL how to mutate data

I have a basic schema for mutating some data which looks like
const schema = new graphql.GraphQLSchema({
mutation: new graphql.GraphQLObjectType({
name: 'Remove',
fields: {
removeUser: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve(_, args) {
const removedData = data[args.id];
delete data[args.id];
return removedData;
},
},
},
})
});
Looking around google I cant find a clear example of the example query which needs to be sent to mutate.
I have tried
POST -
localhost:3000/graphql?query={removeUser(id:"1"){id, name}}
This fails with error:
{
"errors": [
{
"message": "Cannot query field \"removeUser\" on type \"Query\".",
"locations": [
{
"line": 1,
"column": 2
}
]
}
]
}
In order to post requests from the front-end application it is recommended to use apollo-client package. Say i wanted to validate a user login information:
import gql from 'graphql-tag';
import ApolloClient, {createNetworkInterface} from 'apollo-client';
client = new ApolloClient({
networkInterface: createNetworkInterface('http://localhost:3000/graphql')
});
remove(){
client.mutate({
mutation: gql`
mutation remove(
$id: String!
) {
removeUser(
id: $id
){
id,
name
}
}
`,
variables: {
id: "1"
}
}).then((graphQLResult)=> {
const { errors, data } = graphQLResult;
if(!errors && data){
console.log('removed successfully ' + data.id + ' ' + data.name);
}else{
console.log('failed to remove');
}
})
}
More information about apollo-client can be found here
Have you tried using graphiql to query and mutate your schema?
If you'd like to create a POST request manually you might wanna try to struct it in the right form:
?query=mutation{removeUser(id:"1"){id, name}}
(Haven't tried POSTing myself, let me know if you succeeded, i structured this out of the url when using graphiql)
You have to explicitly label your mutation as such, i.e.
mutation {
removeUser(id: "1"){
id,
name
}
}
In GraphQL, if you leave out the mutation keyword, it's just a shorthand for sending a query, i.e. the execution engine will interpret it as
query {
removeUser(id: "1"){
id,
name
}
}
cf. Section 2.3 of the GraphQL Specification
const client = require("../common/gqlClient")();
const {
createContestParticipants,
} = require("../common/queriesAndMutations");
const gql = require("graphql-tag");
const createPartpantGql = async (predictObj) => {
try {
let resp = await client.mutate({
mutation: gql(createContestParticipants),
variables: {
input: {
...predictObj,
},
},
});
let contestParticipantResp = resp.data.createContestParticipants;
return {
success: true,
data: contestParticipantResp,
};
} catch (err) {
console.log(err.message)
console.error(`Error creating the contest`);
return {
success: false,
message: JSON.stringify(err.message),
};
}
};

Resources