I am trying to hit a GraphQL query in Postman but it is giving 400 bad request error.
Query is:
type Query {
school(id: ID, schoolCode: String): School
}
type School {
optionalSubjects: [String!]
code: ID!
defaultSubjects: String!
defaultExams: String!
}
What am I missing here? I am new to GraphQL so I am not getting the cause of this error. Do I need to provide values on the right side of Postman under GRAPHQL VARIABLES section?
400 bad request in graphql
React Apollo GraphQL Mutation returns 400 (bad request)
Graphql POST call gives 400 bad request error
The problem is in the error. It says
Must provide an operation
what you're doing is executing the type definition. That's like executing a function interface definition rather than the function itself in typescript.
As an example, I have a type definition like
const Comment = new GraphQLObjectType({
name: "Comment",
fields: {
id: { type: new GraphQLNonNull(GraphQLID) },
content: { type: new GraphQLNonNull(GraphQLString) },
email: { type: new GraphQLNonNull(GraphQLString) },
createdAt: {
type: new GraphQLNonNull(GraphQLString),
resolve: (source) => source.createdAt.toISOString(),
},
postId: { type: new GraphQLNonNull(GraphQLID) },
},
});
followed by this query
commentMainList: {
type: new GraphQLList(new GraphQLNonNull(Comment)),
resolve: async (source, args, { pgApi }) => {
return pgApi.commentMainList();
},
},
and then in postman I can do
{
commentMainList{
id
postId
email
content
createdAt
}
}
Related
I am using the fastify with fastify-http-proxy with the following configuration:
const proxy = require('fastify-http-proxy');
fastify.register(proxy, {
upstream: "https://myendpoint/occm/api/azure",
prefix: '/azure',
onResponse: (request, reply, res) => {
console.log('Inside onResponse')
reply.send('New body of different length');
},
onError: (reply, error) => {
console.log('Inside onError')
reply.send(`My Error: ${error}`);
},
replyOptions: {
rewriteRequestHeaders: (originalRequest, headers) => {
console.log('Inside rewriteRequestHeaders')
return {
...headers,
};
},
},
});
I have configured a fastify route as this:
fastify.route({
method: 'GET',
url: `/azure/vsa/working-environments`,
schema: {
description: 'Trying out',
tags: ['notifications'],
security: [{
authorization: []
}],
params:{
title: "Working Env GET",
description:'Trying out...',
type: 'object',
example: 'No input',
properties: {
'accountId': {
description: "Account ID",
type: 'string'
},
},
}
},
handler: async (request, reply) => {
}
I am calling it as:
GET http://localhost:xxxx/azure/vsa/working-environments
Basically I would like to proxy the request to this endpoint:
https://myendpoint/occm/api/azure/vsa/working-environments
Now whenever I am running it, getting this error:
{"level":30,"time":1644249539394,"pid":12100,"hostname":"pradipm02-PC","requestId":"Q3T9RjI11N","req":{"method":"GET","url":"/azure/vsa/working-environments","hostname":"localhost:8092","remoteAddress":"127.0.0.1","remotePort":63308},"msg":"incoming request"}
{"level":50,"time":1644249540657,"pid":12100,"hostname":"pradipm02-PC","requestId":"Q3T9RjI11N","err":{"type":"FastifyError","message":"Promise may not be fulfilled with 'undefined' when statusCode is not 204","stack":"FastifyError: Promise may not be fulfilled with 'undefined' when statusCode is not 204\n at C:\\Users\\pradipm\\clients\\CloudManager\\cm_2\\occm\\service-infra\\notifications\\node_modules\\fastify\\lib\\wrapThenable.js:30:30\n at processTicksAndRejections (node:internal/process/task_queues:96:5)","name":"FastifyError","code":"FST_ERR_PROMISE_NOT_FULFILLED","statusCode":500},"msg":"Promise may not be fulfilled with 'undefined' when statusCode is not 204"}
Any of the console.log in the rewriteRequestHeaders, onReponse, onError are not showing up.
Any help is appreciated.
This is discussed in the following thread in fastify-http-proxy forum.
It is suggested to use fastify-reply-from instead.
That solves my issue.
I am following a relay+graphql tutorial and I cam confused with this code:
const GraphQLTodo = new GraphQLObjectType({
name: 'Todo',
fields: {
id: globalIdField('Todo'),
text: {
type: GraphQLString,
resolve: (obj) => obj.text,
},
complete: {
type: GraphQLBoolean,
resolve: (obj) => obj.complete,
},
},
interfaces: [nodeInterface],
});
On the resolve of text and complete field, where does the obj come from? is obj parameter from the query? and also, how do I resolve id from the query? do I do not have to resolve it? for example this is the id field code:
id: {
id: globalIdField('Todo'),
resolve: (obj) => obj.id,
}
Will the above code work and isn't it redundant? how do I properly create the Id field to be relay compliant?
obj is the record itself, usually in whatever format you get from your persistent storage. In this case, it's a Todo object, with at least two properties: text and complete. This makes the resolve functions trivial. Resolve functions exist so you can do more complex things like the following:
fullName: {
type: GraphQLString,
resolve: (obj) => obj.firstName + ' ' + obj.surname,
}
For the Relay compliant id, I don't think you need to do anything. globalIdField does it for you.
By using graphql-js, I need to create graphql schema dynamically by iterating over array of some data, for example:
[{
name: 'author',
fields: [{
field: 'name'
}, {
field: 'books',
reference: 'book'
}]
}, {
name: 'book',
fields: [{
field: 'title'
}, {
field: 'author',
reference: 'author'
}]
}]
The problem is circular references. When I'm creating AuthorType I need BookType to be already created and vise versa.
So resulting schema should look like:
type Author : Object {
id: ID!
name: String,
books: [Book]
}
type Book : Object {
id: ID!
title: String
author: Author
}
How can I solve this?
Quoted from official documentation
http://graphql.org/docs/api-reference-type-system/
When two types need to refer to each other, or a type needs to refer
to itself in a field, you can use a function expression (aka a closure
or a thunk) to supply the fields lazily.
var AddressType = new GraphQLObjectType({
name: 'Address',
fields: {
street: { type: GraphQLString },
number: { type: GraphQLInt },
formatted: {
type: GraphQLString,
resolve(obj) {
return obj.number + ' ' + obj.street
}
}
}
});
var PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: { type: GraphQLString },
bestFriend: { type: PersonType },
})
});
Also look at this related answer of circular Category-Subcategory types
I solved this problem by using a thunk for the fields field.
const User = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: GraphQLID }
})
});
When you make your fields a thunk rather than an object literal you can use types that are defined later in the file.
Please see this post for more info Is there a way to avoid circular type dependencies in GraqhQL?
Based on that post I think this is the correct way to do it.
I'm newbie on Strongloop and I can't find information for how to customize my response class (model schema for a object I built) and I don't know how to show on the API explorer the object with custom data.
For example, I have a custom remote method called score
POST /Challenges/score
I want to show for the parameter data a custom model schema instead of single parameters, not the Model Schema for Challenge, the data on the body have all the parameters and show to the user on the Data Type: Model Schema, is this possible?
{
"id": "string",
"limit": 0,
"order": "string",
"userId": "string"
}
On the other side, in Response Class I want to show the schema for the response object. Something like this:
{
"id":"string",
"userId":"string",
"user": {},
"totalScore":0,
"tags": []
}
I looked different questions (this and this), but can not find something to solve this issues.
Update
Here is the definition of the remote method
Challenge.remoteMethod('score', {
accepts: { arg: 'data', type: 'object', http: { source: 'body' } },
returns: {arg: 'scores', type: 'array'},
http: {path: '/score', verb: 'post'}
});
I believe you might have gone through the official docs of strongloop. If not, here is the link that explains the remote methods and their accepted data types. https://docs.strongloop.com/display/public/LB/Remote+methods
Assuming your custom object is Challenge, to show the object in response you have to specify the type( the type can be one of the loopback's data type or you custom model). So to return Challenge you have to add following code :
Challenge.remoteMethod('score', {
accepts: { arg: 'data', type: 'object', http: { source: 'body' } },
returns: {arg: 'scores', type: 'Challenge', root: true},
http: {path: '/score', verb: 'post'},
});
The second arrow that you have specified is the default values that you want to try out with your API call. You can pass any custom string with default as the key.
For example, if you want to pass some object :
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: 'object',
default: '{
"id": "string",
"userId": "string",
"user": {},
"totalScore": 0,
"tags": []
}',
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
So, for response you can not customize the model. But to pass default values you can put anything in the string format.
In loopback, remote arguments can identify data models which have been defined using ds.define('YourCustomModelName', dataFormat);
so for you case, write a function in a Challenge.js file which will have a remote method (in ur case score) defined.
const loopback = require('loopback');
const ds = loopback.createDataSource('memory');
module.exports = function(Challenge) {
defineChallengeArgFormat() ;
// remote methods (score) defined
};
let defineChallengeArgFormat = function() {
let dataFormat = {
"id": String,
"userId": String,
"user": {},
"totalScore": Number,
"tags": []
};
ds.define('YourCustomModelName', dataFormat);
};
Under remote arguments type use 'type': 'YourCustomModelName'
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: 'YourCustomModelName',
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
You should see it reflecting on explorer after restarting server and refreshing :)
#jrltt, Instead of using default, use object structure pointing to type under accepts and it should work. Note, http source:body is needed.
With random object:
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: {
"id": "string",
"userId": "string",
"user": {},
"totalScore": 0,
"tags": []
},
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
With a defined model which is available in model-config or create using loopback model generator, then that model name can be used to point type.
So lets use User model to show in accepts parameter,
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: 'User',
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
The way I found to solve this problem is to create a new model in this way, with the helper slc loopback: model
? Enter the model name: ArgChallenge
? Select the data-source to attach ArgChallenge to: (no data-source)
? Select model's base class PersistedModel
? Expose ArgChallenge via the REST API? No
? Common model or server only? server
And I continue putting properties, then on Challenge.js:
Challenge.remoteMethod('score', {
accepts: { arg: 'data', type: 'ArgChallenge', http: { source: 'body' } },
returns: {arg: 'scores', type: 'array'},
http: {path: '/score', verb: 'post'}
});
And that works! If anyone knows a better way to do this, please share.
I found a way to solve this problem by changing the type parameter in the accepts array.
when we create remoteMethod; we provide accepts array. there is arg, type, required, http. then we can give our request object into type parameter.
Example code
UserModel.remoteMethod(
'login',
{
description: 'Login a user with username/email and password.',
accepts: [
{
arg: 'credentials',
type: {'email': 'string', 'password': 'string'},
required: true,
http: {source: 'body'},
},
{
arg: 'include', type: ['string'], http: {source: 'query'},
description: 'Related objects to include in the response. ' +
'See the description of return value for more details.',
},
],
returns: {
arg: 'accessToken', type: 'object', root: true,
description:
g.f('The response body contains properties of the {{AccessToken}} created on login.\n' +
'Depending on the value of `include` parameter, the body may contain ' +
'additional properties:\n\n' +
' - `user` - `U+007BUserU+007D` - Data of the currently logged in user. ' +
'{{(`include=user`)}}\n\n'),
},
http: {verb: 'post'},
},
);
I have a simple Sencha Touch 2.1 model class:
Ext.define('App.model.User', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'number', defaultValue: 0 },
{ name: 'first', type: 'string' },
{ name: 'last', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'lastUpdated', type: 'auto' }
]
},
fullName: function () {
var d = this.data,
names = [d.first, d.last];
return names.join(" ");
}
});
A collection of these models is in a store that I've configured to use sencha's Rest Proxy. When I add a new model to the store, and call the Sync method on the store, the new model is posted to a ASP.NET WebAPI Users controller and the following action is hit:
// POST api/Default1
public HttpResponseMessage PostUser(User user)
{
// this is a new user -- get rid of the ID
user.Id = 0;
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.Id }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
The problem is that the a string is being sent as the ID for the model, so it's not passing the ModelState.IsValid check on the controller. This is what's actually getting sent up:
{"id":"ext-record-5","first":"c","last":"d","email":"e","lastUpdated":null}
Any idea why the id field is being set to a string? Also, any idea how I can tell the post action in the Users controller not to validate the id field (as it should be handling creating a new item, it makes sense for the server to create the ID for the new item, not the client).
This link helped me figure it out. The updated model should be:
Ext.define('App.model.User', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'auto', persist: false },
{ name: 'first', type: 'string' },
{ name: 'last', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'lastUpdated', type: 'auto' }
],
},
fullName: function () {
var d = this.data,
names = [d.first, d.last];
return names.join(" ");
}
});
You need to set idProperty on the model. The field for id should also be set to type auto. See documentation here.