I am trying to create a custom resolver to upload an user's avatar image for an Express + Apollo Server + Mongoose project which is using composeWithMongoose.
User resolvers are being created with SchemaComposer
, which generates them from the Mongoose schema like:
import mongoose, { Schema } from 'mongoose';
import timestamps from 'mongoose-timestamp';
import bcrypt from 'mongoose-bcrypt';
import { composeWithMongoose } from 'graphql-compose-mongoose';
export const UserSchema = new Schema(
{
name: {
type: String,
trim: true,
required: true,
},
email: {
type: String,
lowercase: true,
trim: true,
unique: true,
},
phone: {
type: Number,
trim: true,
unique: true,
},
password: {
type: String,
bcrypt: true,
},
type: {
type: String,
},
image: {
type: String,
trim: true,
lowercase: true,
},
},
{
collection: 'users',
}
);
UserSchema.plugin(timestamps);
UserSchema.plugin(bcrypt);
UserSchema.index({ createdAt: 1, updatedAt: 1 });
export const User = mongoose.model('User', UserSchema);
export const UserTC = composeWithMongoose(User);
import { User, UserTC } from '../models/user';
const UserQuery = {
userById: UserTC.getResolver('findById'),
userByIds: UserTC.getResolver('findByIds'),
userOne: UserTC.getResolver('findOne'),
userMany: UserTC.getResolver('findMany'),
userCount: UserTC.getResolver('count'),
userConnection: UserTC.getResolver('connection'),
userPagination: UserTC.getResolver('pagination'),
};
const UserMutation = {
userCreateOne: UserTC.getResolver('createOne'),
userCreateMany: UserTC.getResolver('createMany'),
userUpdateById: UserTC.getResolver('updateById'),
userUpdateOne: UserTC.getResolver('updateOne'),
userUpdateMany: UserTC.getResolver('updateMany'),
userRemoveById: UserTC.getResolver('removeById'),
userRemoveOne: UserTC.getResolver('removeOne'),
userRemoveMany: UserTC.getResolver('removeMany'),
};
export { UserQuery, UserMutation };
According to graphql-compose-mongoose documentation and what I understood I have to add the resolver to the ObjectTypeComposer
(UserTC
) using the method addResolver()
like:
import { User, UserTC } from '../models/user';
import { GraphQLUpload } from 'graphql-upload';
UserTC.addResolver({
name: 'uploadImage',
type: 'String',
args: { userId: 'MongoID!', image: 'Upload!' },
resolve: async ({ source, args, context, info }) => {
const user = await User.findById({ _id: args.userId }).exec();
console.log(user);
}
});
const UserQuery = {
...
};
const UserMutation = {
...
uploadImage: UserTC.getResolver('uploadImage'),
};
export { UserQuery, UserMutation };
This was working until I changed the image
argument type from String
to Upload
. Where, according to Apollo documentation is enabled by default.
I get this error in console: Error: Type with name "Upload" does not exists
.
But I am not sure how to add to my own type definitions using composeWithMongoose()
in this case.
I am interested to hear other methods - maybe using an entirely different approach is completely valid.
I am still getting Upload
type does not exists:
throw new Error(`Type with name ${(0, _misc.inspect)(typeName)} does not exists`); ^
Error: Type with name "Upload" does not exists
express-graphql
and apollo-upload-server
.import dotenv from 'dotenv';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import mongoose from 'mongoose';
import bodyParser from 'body-parser';
import { apolloUploadExpress } from 'apollo-upload-server';
import './utils/db';
import schema from './schema';
dotenv.config();
const app = express();
app.use(
bodyParser.json(),
apolloUploadExpress()
);
const server = new ApolloServer({
schema,
cors: true,
playground: process.env.NODE_ENV === 'development' ? true : false,
introspection: true,
tracing: true,
path: '/',
});
...
import { SchemaComposer } from 'graphql-compose';
import { GraphQLUpload } from 'apollo-upload-server';
import db from '../utils/db'; //eslint-disable-line no-unused-vars
const schemaComposer = new SchemaComposer();
schemaComposer.add(GraphQLUpload);
...
And I am still getting the error: Error: Type with name "Upload" does not exists
.
Well, I just added the custom resolver to the UserMutation
schema and it worked:
const UserMutation = {
userCreateOne: UserTC.getResolver('createOne'),
userCreateMany: UserTC.getResolver('createMany'),
userUpdateById: UserTC.getResolver('updateById'),
userUpdateOne: UserTC.getResolver('updateOne'),
userUpdateMany: UserTC.getResolver('updateMany'),
userRemoveById: UserTC.getResolver('removeById'),
userRemoveOne: UserTC.getResolver('removeOne'),
userRemoveMany: UserTC.getResolver('removeMany'),
uploadImage: {
type: 'String',
args: {
userId: 'String!',
image: 'Upload'
},
resolve: async (_, { userId, image }) => {
const user = await User.findById({ _id: userId}).exec();
console.log(user);
},
}
};
As the docs state:
Note: When using typeDefs, Apollo Server adds scalar Upload to your schema, so any existing declaration of scalar Upload in the type definitions should be removed. If you create your schema with makeExecutableSchema and pass it to ApolloServer constructor using the schema param, make sure to include scalar Upload.
If you're not utilizing the typeDefs
and resolvers
options and instead passing a schema
directly to ApolloServer
's constructor, you have to add the scalar yourself. graphql-compose
's docs show how to do this but it should be as simple as:
import { schemaComposer } from 'graphql-compose'
import { GraphQLUpload } from 'apollo-server'
schemaComposer.add(GraphQLUpload)