graphql-upload

Express/Koa middleware and an Upload scalar for GraphQL multipart file uploads

Library
npm
v18.0.0
1,442stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
82/100Excellent
Development Activity88
Maintenance84
Community56
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture68
Code Quality74
Innovation60
Learning Curve58

graphql-upload is the server-side counterpart to clients like apollo-upload-client: it provides Express and Koa middleware that intercepts incoming GraphQL multipart requests (per the community graphql-multipart-request-spec), parses the multipart form to separate the GraphQL operations JSON from the uploaded file streams, and reassembles them into a single operations object where file variables become Upload scalar promises resolving to readable-stream-backed file objects.

It also exports the GraphQLUpload scalar type itself, so a schema can declare Upload as an input type and resolvers receive fully-formed file objects (filename, mimetype, encoding, and a createReadStream() method) instead of raw multipart parts, regardless of which Node.js GraphQL server framework or client sent the request.

What You Get

  • graphqlUploadExpress — Express middleware that detects and parses multipart GraphQL requests before they reach the GraphQL handler
  • graphqlUploadKoa — the equivalent middleware for Koa applications
  • GraphQLUpload — a ready-to-use GraphQL scalar type for declaring Upload fields/arguments in a schema
  • processRequest — the underlying framework-agnostic request-processing function, usable directly for custom server integrations
  • Upload — the class instances resolvers receive, exposing filename/mimetype/encoding plus a createReadStream() method for streaming file contents to storage

Common Use Cases

  • Accepting file uploads (avatars, documents, media) through a mutation on an Express- or Koa-based GraphQL server
  • Streaming uploaded files directly to cloud storage (S3, GCS) via createReadStream() without buffering the whole file in memory
  • Interoperating with any client implementing the graphql-multipart-request-spec, including apollo-upload-client
  • Integrating GraphQL file uploads into a custom or less common Node.js server setup via the lower-level processRequest function

Under The Hood

Architecture - processRequest.mjs (roughly 400+ lines) is the core: it uses the busboy streaming multipart parser to read the incoming request, extracts the operations JSON part and the map part (which links file field names to JSON-pointer paths inside operations), then walks the map to substitute each referenced path with a Upload instance whose internal promise resolves once the corresponding file part streams in — giving resolvers a value they can await (or access via GraphQLUpload’s parseValue) even though the underlying bytes may still be arriving. graphqlUploadExpress.mjs and graphqlUploadKoa.mjs are thin adapters that call processRequest with the right request/response objects for each framework’s middleware signature, only engaging when the request’s content-type is multipart. Tech Stack - Pure ESM (.mjs, no CJS build), with busboy as the sole non-peer runtime dependency for streaming multipart parsing, and graphql, @types/express, @types/koa as (optional) peer dependencies so the package doesn’t force a specific server framework or pin a GraphQL version. Code Quality - Every module has an adjacent .test.mjs file, with processRequest.test.mjs alone exceeding 1,500 lines covering malformed multipart bodies, oversized files/field counts, aborted uploads, and both Express and Koa integration paths; // @ts-check JSDoc annotations provide type-checking without a separate TypeScript build. API Design - The layered design — a low-level processRequest plus framework-specific graphqlUploadExpress/graphqlUploadKoa wrappers — lets most consumers just drop in one middleware call, while server authors using an unsupported framework can still integrate via the documented lower-level function; the tradeoff is that Upload values are promises-in-disguise (resolved via a hidden .promise) which resolvers must await or otherwise handle correctly, a subtlety documented in the README but easy to miss on first use.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search