GraphQL Express File Upload Middleware
The `graphql-server-express-upload` package provides a middleware for Express.js applications to facilitate file uploads within GraphQL endpoints. Released with version 1.0.0 in 2016, it was designed to integrate specifically with `graphql-server-express` and relies on `multer` for parsing `multipart/form-data` requests. On the client-side, it required companion libraries like `apollo-upload-network-interface` because standard Apollo Client versions at the time did not natively support file uploads. This package introduced an `UploadedFile` scalar and expected a specific resolver implementation. However, both `graphql-server-express` and this package are now considered abandoned, with no updates since their initial release. Modern GraphQL ecosystems, particularly `@apollo/server`, have built-in, more robust file upload capabilities (often leveraging `graphql-upload`), making this package largely obsolete and incompatible with contemporary GraphQL server setups.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'query') / req.body.query is undefined
cause The `graphqlExpressUpload` middleware or `multer` is not correctly placed or configured in the Express middleware chain, preventing the GraphQL query from being parsed.fixEnsure `multer` middleware (e.g., `upload.array('files')`) is applied *before* `graphqlExpressUpload`, and `graphqlExpressUpload` is *before* `graphqlExpress`. Also, verify the `endpointURL` configuration matches your GraphQL endpoint path. -
Unknown type 'UploadedFile'. Did you mean 'Upload'? (or similar type system errors)
cause The `UploadedFile` scalar type has not been defined in your GraphQL schema or its associated resolver is missing or incorrectly registered with your GraphQL server.fixAdd `scalar UploadedFile` to your GraphQL schema definition (typeDefs) and ensure the `UploadedFile` resolver (with `__parseLiteral`, `__serialize`, `__parseValue` methods) is correctly provided to your GraphQL server setup. -
ApolloError: Variable "$files" got invalid value [object Object]; Expected type UploadedFile! (or similar type mismatch for file arguments)
cause The client-side is not sending files in the expected `multipart/form-data` format, or the `UploadedFile` scalar resolver is misconfigured, leading to a type mismatch during validation.fixVerify that your client-side code uses a network interface (e.g., `apollo-upload-network-interface` for older Apollo Client versions, or modern `apollo-link-http` with `apollo-upload-client`) that correctly handles `multipart/form-data`. Additionally, double-check the `UploadedFile` resolver logic for any issues.
Warnings
- breaking This package is built for `graphql-server-express`, which is deprecated. It is fundamentally incompatible with modern Apollo Server versions (@apollo/server) and recent GraphQL.js versions (v15+). Attempting to use it will likely result in critical runtime errors.
- deprecated The underlying `graphql-server-express` library, for which this package was developed, is deprecated and no longer maintained. Consequently, `graphql-server-express-upload` also inherits this deprecated status.
- gotcha This middleware requires explicit integration with `multer` for `multipart/form-data` parsing *before* `graphqlExpressUpload`. Incorrect `multer` configuration (e.g., wrong field name for files, missing destination) will prevent file uploads from being processed correctly.
- gotcha The `UploadedFile` scalar resolver provided in the documentation is a basic JSON parser. It does not inherently handle file storage or processing; it merely makes the processed file metadata (from `multer`) available in your GraphQL mutation's `files` argument.
Install
-
npm install graphql-server-express-upload -
yarn add graphql-server-express-upload -
pnpm add graphql-server-express-upload
Imports
- graphqlExpressUpload
import { graphqlExpressUpload } from 'graphql-server-express-upload';import graphqlExpressUpload from 'graphql-server-express-upload';
- UploadedFile
scalar UploadedFile
- Kind
import { Kind } from 'graphql/language/kinds';import { Kind } from 'graphql';
Quickstart
import express from 'express';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';
import graphqlExpressUpload from 'graphql-server-express-upload';
import multer from 'multer';
import { Kind } from 'graphql';
const app = express();
const port = 4000;
// Multer setup for file uploads
const upload = multer({
dest: '/tmp/uploads', // Ensure this directory exists or create it
});
// GraphQL Schema Definition
const typeDefs = `
scalar UploadedFile
type ProfilePicture {
id: Int!
url: String
thumb: String
square: String
small: String
medium: String
large: String
full: String
}
type Query {
hello: String
}
type Mutation {
uploadProfilePicture(id: Int!, files: [UploadedFile!]!): ProfilePicture
}
`;
// Utility for UploadedFile scalar resolver (as shown in package README)
function parseJSONLiteral(ast) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT: {
const value = Object.create(null);
ast.fields.forEach(field => {
value[field.name.value] = parseJSONLiteral(field.value);
});
return value;
}
case Kind.LIST:
return ast.values.map(parseJSONLiteral);
default:
return null;
}
}
// GraphQL Resolvers
const resolvers = {
UploadedFile: {
__parseLiteral: parseJSONLiteral,
__serialize: value => value,
__parseValue: value => value,
},
Query: {
hello: () => 'Hello from GraphQL!',
},
Mutation: {
async uploadProfilePicture(root, { id, files }, context) {
console.log('Received upload for profile picture:', { id, files });
// 'files' here would be an array of objects provided by Multer/graphqlExpressUpload
// In a real app, process these files (e.g., save to disk, cloud storage).
const uploadedFile = files[0]; // Example: assuming one file
return {
id,
url: `http://example.com/uploads/${uploadedFile?.filename || 'dummy.jpg'}`,
thumb: `http://example.com/thumbs/${uploadedFile?.filename || 'dummy.jpg'}`,
square: `http://example.com/square/${uploadedFile?.filename || 'dummy.jpg'}`,
small: `http://example.com/small/${uploadedFile?.filename || 'dummy.jpg'}`,
medium: `http://example.com/medium/${uploadedFile?.filename || 'dummy.jpg'}`,
large: `http://example.com/large/${uploadedFile?.filename || 'dummy.jpg'}`,
full: `http://example.com/full/${uploadedFile?.filename || 'dummy.jpg'}`
};
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
// Express app setup for GraphQL endpoint
app.use(
'/graphql',
upload.array('files'), // Multer middleware to handle 'files' input field
graphqlExpressUpload({ endpointURL: '/graphql' }), // THIS PACKAGE'S MIDDLEWARE
graphqlExpress((req) => ({
schema,
context: {
req, // Access to the original request
},
}))
);
// GraphiQL endpoint for testing
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
})
);
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
console.log(`GraphQL endpoint: http://localhost:${port}/graphql`);
console.log(`GraphiQL endpoint: http://localhost:${port}/graphiql`);
});