0
votes

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:

User Model

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);

User Schema

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:

User Schema

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.

What would you do? Maybe using an entire different approach is completely valid. Any comments or suggestions will be appreciated.

Edit 1: In response to @Daniel Rearden

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

  1. I have installed: express-graphql and apollo-upload-server.
  2. I added the middlewares to the server configuration:
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: '/',
});
...
  1. I have provided the following code inside the Schema index:
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.

Edit 2: Also, in response to @Daniel Rearden

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);
        },
    }
};
1

1 Answers

2
votes

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)