I have a simple graphQL server
import { ApolloServer } from "#apollo/server";
import { startStandaloneServer } from "#apollo/server/standalone";
const typeDefs = `#graphql
type Task {
id: String
name: String
completed: Boolean
}
type Query {
tasks: [Task]
}
`;
const tasks = [
{ id: "todo-1", name: "Eat", completed: true },
{ id: "todo-2", name: "Sleep", completed: false },
{ id: "todo-3", name: "Repeat", completed: false },
];
const resolvers = {
Query: {
tasks: () => tasks,
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4002 },
});
console.log(`🚀 Server ready at: ${url}`);
I want this server to connect to gRPC server which has the CRUD operation implementations, and I want to perform those CRUD operations using graphQL queries and resolvers since I will be connecting this graphQL server with the frontend part
proto file:
syntax = "proto3";
option java_package = "com.practice.grpc";
option java_outer_classname = "Todos";
service TodoList{
rpc viewTodos(Empty) returns(stream Todo);
rpc editTodo(Todo) returns(APIResponse);
rpc addTodo(Todo) returns (APIResponse);
rpc deleteTodo(Todo) returns (APIResponse);
}
message Todo{
int32 id = 1;
string title = 2;
string editTitle = 3; //optional
bool completed = 4;
}
message APIResponse{
string responseMessage = 1;
int32 responseCode = 2;
}
message Empty{}
message ErrorResponse {
string title = 1;
int32 responseCode = 2;
}
Related
I want to save a token in the redis cache after user sign-in in my app.
The cache config.service.ts file:
#Injectable()
export class CacheConfigService {
constructor(private configService: ConfigService) {}
get url(): string {
console.log(this.configService.get<string>('redis.url'));
return this.configService.get<string>('redis.url') as string;
}
}
The cacheModule file:
#Module({
imports: [
CacheModule.register<RedisClientOptions>({
// #ts-ignore
store: createRedisStore,
socket: {
host: 'localhost',
port: 6379,
},
isGlobal: true,
// url: 'redis://' + process.env.REDIS_HOST + ':' + process.env.REDIS_PORT,
}),
],
})
The user signin method:
emailPasswordSignInPOST: async (input: any) => {
if (
originalImplementation.emailPasswordSignInPOST === undefined
) {
throw Error('Should never come here');
}
let response: any =
await originalImplementation.emailPasswordSignInPOST(input);
const formFields: any = input.formFields;
const inputObject: any = {};
for (let index = 0; index < formFields.length; index++) {
const element = formFields[index];
inputObject[element.id] = element.value;
}
const { email } = inputObject;
const user = await this.userService.findOneByEmail(email);
const id = user?.id;
const payload = { email, id };
const secret = 'mysecret';
const token = jwt.sign(payload, secret, {
expiresIn: '2h',
});
response.token = token;
const cacheKey = 'Token';
await this.redisClient.set(cacheKey, token, {
EX: 60 * 60 * 24,
});
return response;
},
Please note that this is a different module.
When I send signin request from postman than it logs this error in the console: "Error: The client is closed"
I ran the app with the command DEBUG=* npm run start:dev to see the logs about redis but there is no log about redis.
I have an issue with subscription can't be unsubscribe.
Before we start, this is my setup: Apollo Client(graphql-ws) <-> Apollo Server(graphql-ws). On the server, I build a custom PubSub instead of using the one provided.
As you can see here, the client has sent a complete request to server with the id. However, the server is still sending more data to it. I have read somewhere that you have to send GQL_STOP, aka STOP instead. However, this is what Apollo Client is sending.
A bit of code:
Client subscription:
export const useGetDataThroughSubscription = (
resourceIds: number[],
startDate?: Date,
endDate?: Date
) => {
const variables = {
startTime: startDate?.toISOString() ?? '',
endTime: endDate?.toISOString() ?? '',
resourceIds,
};
return useGetDataSubscription({
variables,
...
})
}
Server pubsub:
const createPubSub = <TopicPayload extends { [key: string]: unknown }>(
emitter: EventEmitter = new EventEmitter()
) => ({
publish: <Topic extends Extract<keyof TopicPayload, string>>(
topic: Topic,
payload: TopicPayload[Topic]
) => {
emitter.emit(topic as string, payload);
},
async *subscribe<Topic extends Extract<keyof TopicPayload, string>>(
topic: Topic,
retrievalFunc: (value: TopicPayload[Topic]) => Promise<any>
): AsyncIterableIterator<TopicPayload[Topic]> {
const asyncIterator = on(emitter, topic);
for await (const [value] of asyncIterator) {
const data = await retrievalFunc(value);
yield data;
}
},
Server subscribe to event:
const resolver: Resolvers = {
Subscription: {
[onGetAllLocationsEvent]: {
async *subscribe(_a, _b, ctx) {
const locations = await ...;
yield locations;
const iterator = ctx.pubsub.subscribe(
onGetAllLocationsEvent,
async (id: number) => {
const location = ...;
return location;
}
);
for await (const data of iterator) {
if (data) {
yield [data];
}
}
},
resolve: (payload) => payload,
},
},
};
In this one, if instead of the for loop, I return iterator instead, then the server will send back a complete and stop the subscription all together. That's great, but I want to keep the connection open until client stop listening.
And server publish
ctx.pubsub.publish(onGetAllResourcesEvent, resource.id);
So how should I deal with this?
Hello I want to use federation.I followed this tutorial. I can start my subgraph but when I start my gateway, I get this error:
Error: A valid schema couldn't be composed. The following composition errors were found:
Cannot extend type "Query" because it is not defined. Did you mean "User"?
Cannot extend type "Mutation" because it is not defined.
I even extended Query and Mutation but got another error.
my gateway code:
import fastify from "fastify";
import { ApolloServer } from "apollo-server-fastify";
import { ApolloGateway } from "#apollo/gateway";
const PORT = process.env.PORT || 4000;
const IP = "0.0.0.0";
const app = fastify({ trustProxy: true });
const gateway = new ApolloGateway({
serviceList: [{ name: "amazon", url: "http://localhost:4001/graphql" }],
});
(async () => {
try {
const { schema, executor } = await gateway.load();
const server = new ApolloServer({ schema, executor });
server.start().then(() => {
app.register(server.createHandler({ path: "/graphql" }));
app.listen(PORT, IP, (err) => {
if (err) {
console.error(err);
} else {
console.log("Server is ready at port 4000");
}
});
});
} catch (error) {
console.log("dick");
console.log("err", error);
}
})();
my schema in subgraph:
type Query {
getUsers: [User]!
}
type Mutation {
createUser(name: String!): Boolean!
}
type User #key(fields: "id") {
id: ID!
name: String!
}
Most likely you're using graphql version 16. Following https://www.apollographql.com/docs/federation/gateway/ you must use 15 for now (December 2021). Try:
yarn add graphql#15
Another detail you can do on latest versions is simply pass the gateway to ApolloServer, like this:
const server = new ApolloServer({ gateway });
I'm writing an Apollo server plugin for node.js, and my goal is to improve my teams debugging experience. My plugin currently looks something like this:
export function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
// Set requestId on the header
const requestId = (requestContext?.context as EddyContext)?.requestId;
if (requestId) {
requestContext.response?.http?.headers.set('requestId', requestId);
}
return {
willSendResponse(context) { // <== Where do I find the "path" in the schema here?
// Inspired by this: https://blog.sentry.io/2020/07/22/handling-graphql-errors-using-sentry
// and the official documentation here: https://docs.sentry.io/platforms/node/
// handle all errors
for (const error of requestContext?.errors || []) {
handleError(error, context);
}
},
};
},
};
}
I would like to know if I can access the path in the schema here? It's pretty easy to find the name of mutaiton/query with operation.operationName, but where can I get the name of the query/mutation as defined in the schema?
Solution
export function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
// Set requestId on the header
const requestId = (requestContext?.context as EddyContext)?.requestId;
if (requestId) {
requestContext.response?.http?.headers.set('requestId', requestId);
}
return {
didResolveOperation(context) {
const operationDefinition = context.document
.definitions[0] as OperationDefinitionNode;
const fieldNode = operationDefinition?.selectionSet
.selections[0] as FieldNode;
const queryName = fieldNode?.name?.value;
// queryName is what I was looking for!
},
};
},
};
}
Your requirement is not very clear. If you want to get the name of the query/mutation to distinguish which query or mutation the client sends.
You could get the name from context.response.data in willSendResponse event handler.
E.g.
server.ts:
import { ApolloServer, gql } from 'apollo-server';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
import { parse, OperationDefinitionNode, FieldNode } from 'graphql';
function eddyApolloPlugin(): ApolloServerPlugin {
return {
requestDidStart(requestContext) {
return {
didResolveOperation(context) {
console.log('didResolveOperation');
const obj = parse(context.request.query!);
const operationDefinition = obj.definitions[0] as OperationDefinitionNode;
const selection = operationDefinition.selectionSet.selections[0] as FieldNode;
console.log('operationName: ', context.request.operationName);
console.log(`${context.operation!.operation} name:`, selection.name.value);
},
willSendResponse(context) {
console.log('willSendResponse');
console.log('operationName: ', context.request.operationName);
console.log(`${context.operation!.operation} name:`, Object.keys(context.response.data!)[0]);
},
};
},
};
}
const typeDefs = gql`
type Query {
hello: String
}
type Mutation {
update: String
}
`;
const resolvers = {
Query: {
hello() {
return 'Hello, World!';
},
},
Mutation: {
update() {
return 'success';
},
},
};
const server = new ApolloServer({ typeDefs, resolvers, plugins: [eddyApolloPlugin()] });
const port = 3000;
server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`));
GraphQL Query:
query test {
hello
}
the logs of the server:
didResolveOperation
operationName: test
query name: hello
willSendResponse
operationName: test
query name: hello
GraphQL Mutation:
mutation test {
update
}
the logs of the server:
didResolveOperation
operationName: test
mutation name: update
willSendResponse
operationName: test
mutation name: update
I'm just starting with graphql and having a few problems calling a basic query in the playground.
I have a server.js
const mongoose = require('mongoose');
require('dotenv').config({ path: 'variables.env' })
const { ApolloServer } = require('apollo-server')
//Monogoose schemas
const Recipe = require('./models/Recipe');
const { typeDefs } = require('./schema');
const { resolvers } = require('./resolvers')
const server = new ApolloServer({
typeDefs,
resolvers
})
// Connect to DB
mongoose
.connect(process.env.MONGO_URI, { autoIndex: false })
.then(() => {
console.log('DB connected')
})
.catch(err => console.error(err))
server.listen().then(({ url }) => {
console.log(`server listening on ${url}`)
})
a schema.js
const gql = require('graphql-tag');
exports.typeDefs = gql`
type Recipe{
_id: ID
name: String!
category: String!
description: String!
instructions: String!
createdDate: String
likes: Int
username: String
}
type Query {
getAllRecipes: [Recipe]
}
`
a resolvers.js
exports.resolvers = {
Query: {
getAllRecipes: async (root, args, { Recipe }) => {
const allRecipes = await Recipe.find()
return allRecipes
}
},
and a mongoose schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema
const RecipeSchema = new Schema({
name: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
instructions: {
type: String,
required: true
},
createdDate: {
type: Date,
default: Date.now
},
likes: {
type: Number,
default: 0
},
username: {
type: String
}
})
module.exports = mongoose.model('Recipe', RecipeSchema)
The server and connection to the DB work and when I open the playground the schema is shown. I have data in the DB
When I run query:
query{
getAllRecipes{
name
}
}
I get an error "Cannot read property 'find' of undefined",
Can anyone see what I'm doing wrong here.
On your resolvers.js file you're expecting Recipe to come from the context argument, but you did not add it anywhere from what I can tell from your snippets.
During the initialization of your ApolloServer instance, you can pass a context property that will be injected on all resolvers:
...
const server = new ApolloServer({
typeDefs,
resolvers,
context: {
Recipe,
},
});
The context property can also be a function that returns an object at the end, in case you want something more elaborated.
For more details on the option and also on others you can pass, see: https://www.apollographql.com/docs/apollo-server/api/apollo-server/.