Importing GraphQL resolvers and schema triggers error - javascript

When I am trying to run server with following configuration I receive an error:
Error: "createUser" defined in resolvers, but has invalid value "function (userInput) {...
The resolver's value must be of type object.
index.ts
const schema = loadSchemaSync(join(__dirname, './schema/**.graphql'), {
loaders: [new GraphQLFileLoader()]
})
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers: {
...UserResolvers
}
})
.graphql schema
# import User, UserInput from "User.graphql"
type RootQueries {
user(id: String!): User
}
type RootMutations {
createUser(userInput: UserInput): User
}
schema {
query: RootQueries
mutation: RootMutations
}
resolvers
const UserResolvers = {
async createUser(userInput: UserInput) {
// some code
}
}
export { UserResolvers }

You're probably looking for
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers: {
RootMutations: UserResolvers
}
})
as resolvers are usually grouped by the type they appear on.

Related

Apollo Federation: A valid schema couldn't be composed. The following composition errors were found: Syntax Error: Unexpected character: U+0130

I tried to use Apollo Federation and I create an user service and gateway. User service is working alone fine but Gateway is giving an error:
Error: A valid schema couldn't be composed. The following composition errors were found:
Syntax Error: Unexpected character: U+0130.
at IntrospectAndCompose.createSupergraphFromSubgraphList
Here is my code;
GATEWAY
import { ApolloGateway, IntrospectAndCompose } from "#apollo/gateway";
import { ApolloServer } from "#apollo/server";
import { expressMiddleware } from "#apollo/server/express4";
import { ApolloServerPluginDrainHttpServer } from "#apollo/server/plugin/drainHttpServer";
import express from "express";
import http from "http";
import { json } from "body-parser";
const startServer = async () => {
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: "userService", url: "http://localhost:4001" }
],
}),
});
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
gateway,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve)
);
console.log(`🚀 Server ready at http://localhost:4000/graphql`);
};
startServer();
USER SERVICE
index.ts
import { ApolloServer } from "#apollo/server";
import { buildSubgraphSchema } from "#apollo/subgraph";
import gql from "graphql-tag";
import { resolvers } from "./graphql/resolver";
import { startStandaloneServer } from "#apollo/server/standalone";
import { readFileSync } from "fs";
const mongoose = require("mongoose");
const typeDefs = gql(
readFileSync("src/graphql/types/user.graphql", { encoding: "utf-8" })
);
import { DB_URI } from "../database-config";
require("dotenv").config();
const createDatabaseConnection = async () => {
try {
await mongoose.connect(DB_URI);
console.log("User Service: Connected to DB");
} catch (error) {
console.log(error);
}
};
const startUserServiceServer = async () => {
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
await createDatabaseConnection();
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
});
console.log(`User Service: Server ready at ${url}`);
};
startUserServiceServer();
user.graphql
extend schema
#link(url: "https://specs.apollo.dev/federation/v2.0", import: ["#key"])
type Query {
me: User
}
type Mutation {
addUser(fields: UserInput): User
updateUser(fields: UserInput): User
}
type User #key(fields: "id") {
id: ID
fullName: String
phone: String
avatarURL: String
password: String
email: String
address: String
createdAt: String
updatedAt: String
}
input UserInput {
id: ID
fullName: String
phone: String
avatarURL: String
password: String
email: String
address: String
}
I tried to change encoding style of user.graphl as utf16lebut it didn't work. Also I use Turkish as a default language on my computer. When I switched to English the problem was gone but I couldn't understand what the problem is.
The Name in GraphQL is defined to be limited to the regular expression /[_A-Za-z][_0-9A-Za-z]*/.
The Turkish captial I (U+0130 or İ) does not match this regular expression and is considered invalid.
[source: GraphQL Spec]

Prisma throws an error "TypeError: cannot read property findmany of undefined"

I wanted to make a chat app to practice working with graphql and node, for database I used prisma. I was doing everything like in this tutorial.
https://www.howtographql.com/graphql-js/0-introduction/
I just changed variable names.
so I have this code
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
const resolvers = {
Query: {
history: async (parent, args, context) => {
return context.prisma.Messages.findMany()
},
},
Mutation: {
post: (parent, args, context) => {
const newMessage = context.prisma.Messages.create({
data: {
username: args.username,
message: args.message,
},
})
return newMessage
},
},
}
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context: {
prisma,
}
})
server.start(() => console.log(`Server is running on http://localhost:4000`))
as my index.js
this is my schema.prisma
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model Message {
id Int #id #default(autoincrement())
sendedAt DateTime #default(now())
message String
username String
}
script.js
const { PrismaClient } = require("#prisma/client")
const prisma = new PrismaClient()
async function main() {
const newMessage = await prisma.Messages.create({
data: {
message: 'Fullstack tutorial for GraphQL',
username: 'www.howtographql.com',
},
})
const allMessages = await prisma.Messages.findMany()
console.log(allMessages)
}
main()
.catch(e => {
throw e
})
// 5
.finally(async () => {
await prisma.disconnect()
})
and schema.graphql
type Query {
history: [Message!]!
}
type Mutation {
post(username: String!, message: String!): Message!
}
type Message {
id: ID!
message: String!
username: String!
}
and that is what i got in my playground
"data": null,
"errors": [
{
"message": "Cannot read property 'findMany' of undefined",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"history"
]
}
]
}
please help
I managed to fix that. Actually, all I needed was to use the same name but lowercased as in schema.prisma
It should be noted it is not actually lowercase but camel case.
Example:
Model name: Message -> Prisma client name: message
Model name: MessagePerUser -> Prisma client name: messagePerUser
Would like to add on to the answer
Basically when calling await prisma.User.create, prisma allows the User model to be in caps, but when calling other models such as prisma.messages.create the m has to be in lowercase. Essentially,for all prisma calls, the models should be in lowercase. Hopefully this answers your question, prisma does not flag this error out
use Table name as camelCase in prisma Client to make Queries
Example:
Table Name is: StudentData
then use camelCase according to table name
prisma.studentData.findMany();

What should I do to make TypeORM, Type GraphQL, Apollo Server, and Azure Functions compatible

I have the following Azure Function written in typescript
import { createConnection, getConnectionManager } from 'typeorm';
import { ApolloServer, gql } from 'apollo-server-azure-functions';
import { buildSchemaSync, buildSchema } from 'type-graphql';
import { GraphQLSchema } from 'graphql';
import { AzureFunction, Context, HttpRequest } from '#azure/functions';
import 'reflect-metadata';
import ProjectResolver from './data-layer/project/project.resolver';
import TaskResolver from './data-layer/task/task.resolver';
import { Project } from './models/entity/project/project.model';
import { Task } from './models/entity/task/task.model';
const typeDefs = gql`
type Project {
projectId: ID!
name: String
projectHandler: String
documentFolderId: Int
tasks: [Task]
}
type Task {
taskId: ID!
title: String
primarySearchEntityId: String
project: Project
}
type Query {
projects: [Project]
tasks: [Task]
}
`;
let ready = false;
// first promise
let schema: GraphQLSchema;
buildSchema({
resolvers: [
ProjectResolver,
TaskResolver
]
}).then(success => {
schema = success;
ready = true;
}).catch(() => {
throw "Something failed"
});
while(!ready) {
}
ready = false;
//second promise
createConnection({
type: "mssql",
host: "xxx",
port: xxxx,
username: "xxxx",
password: "xxxx",
database: "xxxx",
entities: [
Project,
Task
],
extra: {
options: {
encrypt: true
},
},
synchronize: false,
logging: false
}).then(() => {
ready = true;
})
.catch(() => {
throw "Something failed"
});
while(!ready) {
}
const server = new ApolloServer({ typeDefs, schema, debug: true });
export default server.createHandler();
My Apollo Server in this case needs the schema to exist to be exported. The schema can't exist until the promise in "buildSchema" resolves, and the resolvers won't work until the database connection is established in "createConnection". Here is my struggle, async-await would fix this, but I can't await the promises because they are in a top-level module. I tried the while loop, but that is apparently a locking operation, so the promises won't resolve until the while loop is finished executing (sort of a catch 22 here). And that seems like a huge hack anyways. So is there a way at the top level, I can ensure both promises resolve BEFORE exporting my Apollo handler? There is a synchronous way to build the schema with the TypeGraphQL library I can use, but I'm a little hosed here waiting for my database connection to succeed? It seems that all of these technologies are compatible with all of the others, just not all 4 simultaneously.
I was able to get around with using an asynchronous context:
const server = new ApolloServer({
typeDefs,
resolvers,
context: async () => ({
db: await createConnection(...),
})
}));
Wrap your logic in a top-level async function.
Make await calls as needed.
Return a promise from the top-level function which resolves with ApolloServer's response.
...
let server: ApolloServer
const httpTrigger: AzureFunction = async function (context: Context) {
if (server === undefined) {
const conn = await createConnection(...)
const schema = await buildSchema(...)
server = new ApolloServer({
schema,
})
}
const apolloHandler = server.createHandler()
return new Promise((resolve, reject) => {
const originalDone = context.done
context.done = (error, result) => {
originalDone(error, result)
if (error) {
reject(error)
}
resolve(result)
}
apolloHandler(context, context.req!)
})
}
export default httpTrigger
Here's a starter repo with a full example of using Apollo Server, TypeGraphQL, TypeORM and Azure together: azure-function-graphql-typescript-starter (I'm the author)

Express TypeScript: Why is my controller method throwing error message?

folks. I'm just getting started with TypeScript and, thus, part of getting my feet wet includes converting a Express backend I built to TS. All has been well to this point, but now I got some rather unusual issues. Specifically, the lines const hasVoted = poll.votedBy.some((voter): boolean => voter.equals(voterId)); and const choice = await poll.choices.id(choiceId); in the code snippet below, which result in Property 'equals' does not exist on type 'string' and Property 'choices' does not exist on type 'Poll & Document' error messages respectively. For reference, I has the JS version working just fine, so what is it that I may be missing here?
Post Controller
import * as mongoose from 'mongoose';
import { Request, Response } from 'express';
import { Poll } from '../models/index';
class PollsController {
public async addNewPoll(req: Request, res: Response) {
// ...
}
public async voteInPoll(req: Request, res: Response) {
const { category, pollId } = req.params;
const { name, choiceId, voterId } = req.body;
try {
const poll = await Poll.findById(pollId);
// Check if user has already voted in poll
const hasVoted = poll.votedBy.some((voter): boolean => voter.equals(voterId));
if (!voterId) {
res
.status(401)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res
.status(401)
.json({ message: 'Sorry, you have already participated in poll' });
} else {
const choice = await poll.choices.id(choiceId);
const votedChoice = { name, votes: choice.votes + 1 };
await choice.set(votedChoice);
await poll.votedBy.push(voterId);
poll.save();
res
.status(200)
.json({
message: 'Thank you for voting.',
poll,
});
}
} catch (error) {
res.status(404).json({ error });
}
}
// ...
}
export default PollsController
Poll Interface
interface Poll {
title: string;
category: string;
addedBy: string;
votedBy?: [string];
}
export default Poll;
Poll Schema
import * as mongoose from 'mongoose';
import PollInterface from './poll.interface';
const Schema = mongoose.Schema;
const ChoiceSchema = new Schema({
name: { type: String },
votes: { type: Number }
});
const PollSchema = new Schema({
title: { type: String },
category: { type: String },
choices: [ChoiceSchema],
addedBy: { type: Schema.Types.ObjectId, ref: 'User' },
votedBy: [{ type: Schema.Types.ObjectId, ref: 'User' }]
},{
timestamps: true,
});
const Poll = mongoose.model<PollInterface & mongoose.Document>('Poll', PollSchema);
export default Poll;
Edit: Included both Poll interface and schema code snippets
For the first error, your interface defines votedBy as an array of strings. You can call .some on an array but then you are calling .equals on a string which is not a method on type string. You can change
const hasVoted = poll.votedBy.some((voter): boolean => voter.equals(voterId));
to
const hasVoted = poll.votedBy.some((voter): boolean => voter === voterId);
And the second one, you do not define choices on your Poll interface. So the following line fails the typescript compiler because of it.
const choice = await poll.choices.id(choiceId);
You need to add choices as property on your Poll interface. I don't know exactly what your implementation is but you would want to add something like the following to your Poll interface.
choices: {
id:() => string;
}
From your code, it looks like you are trying to find a matching choice from a given choiceId.
Property 'equals' does not exist on type 'string': if it works in JS, it mean voter is not a 'string' as you declared.
Check the signature of your method Poll.findById(pollId).

How to execute GraphQL query from server

I am using graphql-express to create an endpoint where I can execute graphql queries in. Although I am using Sequelize with a SQL database it feels wrong to use it directly from the server outside of my graphql resolve functions. How do I go about querying my graphql API from the same server as it was defined in?
This is how I set up my graphql endpoint:
const express = require('express');
const router = express.Router();
const graphqlHTTP = require('express-graphql');
const gqlOptions = {
schema: require('./schema')
};
router.use('/', graphqlHTTP(gqlOptions));
modules.exports = router;
Basically what I want is to be able to do something like this:
query(`
{
user(id: ${id}) {
name
}
}
`)
How would I create this query function?
GraphQL.js itself does not require a http server to run. express-graphql is just a helper to mount the query resolver to a http endpoint.
You can pass your schema and the query to graphql, it'll return a Promise that'll resolve the query to the data.
graphql(schema, query).then(result => {
console.log(result);
});
So:
const {graphql} = require('graphql');
const schema = require('./schema');
function query (str) {
return graphql(schema, str);
}
query(`
{
user(id: ${id}) {
name
}
}
`).then(data => {
console.log(data);
})
I would like to complete the answer from #aᴍɪʀ by providing the pattern for properly doing a query / mutation with parameters:
const params = {
username: 'john',
password: 'hello, world!',
userData: {
...
}
}
query(`mutation createUser(
$username: String!,
$password: String!,
$userData: UserInput) {
createUserWithPassword(
username: $username,
password: $password,
userData: $userData) {
id
name {
familyName
givenName
}
}
}`, params)
This way, you don't have to deal with the string construction bits " or ' here and there.
Thanks for the other answers, this is for Nextjs inside getServerSideProps, getStaticProps, getStaticPaths and getStaticProps, includes context for MongoDB. Need this because if you have your graphql sever in api route, when you build it wont build because your server in api route is not running.
Mongo file: plugin/zDb/index:
import {MongoClient} from "mongodb"
export const connectToDatabase = async() => {
const client = new MongoClient(process.env.MONGODB_URI, {useNewUrlParser: true, useUnifiedTopology: true})
let cachedConnection
if(cachedConnection) return cachedConnection
try {
const connection = await client.connect()
cachedConnection = connection
return connection
} catch(error) {
console.error(error)
}
}
export const mongoServer = async() => {
const connect = await connectToDatabase()
return connect.db(process.env.DB_NAME)
}
In pages folder, eg index.js file homepage:
import {graphql} from 'graphql'
import {schema} from '#/plugin/zSchema/schema'
import {mongoServer} from '#/plugin/zDb/index'
async function query(source, variableValues) {
return graphql({schema, source, contextValue: {mongo: await mongoServer()}, variableValues})
}
export async function getServerSideProps(ctx) {
const listingCurrent = await query(`query($keyField: String, $keyValue: String) {
ListingRQlistingListKeyValue(keyField: $keyField, keyValue: $keyValue) {
address
urlSlug
imageFeature {
photoName
}
}
}`, {
keyField: 'offerStatus'
, keyValue: 'CURRENT'
})
return {props: {
listingCurrent: listingCurrent.data.ListingRQlistingListKeyValue
}
}
}
Please note: the graphql call field names is from: https://github.com/graphql/graphql-js/blob/fb27b92a5f66466fd8143efc41e1d6b9da97b1f4/src/graphql.js#L62
export type GraphQLArgs = {|
schema: GraphQLSchema,
source: string | Source,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?ObjMap<mixed>,
operationName?: ?string,
fieldResolver?: ?GraphQLFieldResolver<any, any>,
|};
And my schema file: plugin/zSchema/schema.js
import { makeExecutableSchema } from '#graphql-tools/schema'
import {resolvers} from '#/plugin/zSchema/resolvers'
import {typeDefs} from '#/plugin/zSchema/typeDefs'
export const schema = makeExecutableSchema({resolvers, typeDefs})
The #/plugin folder: I'm using this in root file called jsconfig.json, and I put all my folders inside root/plugin, and I call it with #/plugin. You can use your own folder structure importing them as how you normally do it.
{
"compilerOptions": {
"baseUrl": "."
, "paths": {
"#/*": ["./*"]
}
}
}

Categories