jaydenseric / graphql-upload

Middleware and an Upload scalar to add support for GraphQL multipart requests (file uploads via queries and mutations) to various Node.js GraphQL servers.
https://npm.im/graphql-upload
MIT License
1.43k stars 131 forks source link

Getting the dimensions (height and width) of a graphql-upload file upload in resolver #251

Closed alex-r89 closed 3 years ago

alex-r89 commented 3 years ago

Hi there,

Apologies, this isn't really an issue, but I was wondering if it's possible to get the height and width of a file uploaded with graphql-upload. Furthering that, if it is possible, how would this be done.

I have graphql-upload implemented and working - my front end form passes the object to my resolver in the following format:

interface FileUpload {
  filename: string
  mimetype: string
  encoding: string
  createReadStream: () => Readable
}

@InputType()
class FileUploadInput {
  @Field((type) => GraphQLUpload)
  imageUpload: FileUpload
}

@Resolver()
export class UploadImageResolver {
@Mutation(() => GenericResponse)
async uploadImage( @Arg('input'){ imageUpload }: FileUploadInput ): Promise<GenericResponse> {

const { createReadStream } = await imageUpload

// do something with this createReadStream to get the height/width?

I have taken out the code but I currently store the image using another service. However I would like to check the dimensions of this image match a maximum allowed height and width and was wondering if this is possible/how it would be achieved.

Thanks,

jaydenseric commented 3 years ago

It's best to encourage users to upload the biggest images they can, store the original image, and use a thumbnailing service such as imgix to create an optimized thumbnail in the required formats (e.g. WebP with a PNG fallback) whenever a particular size is needed. You could try to build your own thumbnailing server, but it's likely not worth the trouble.

When resolving the upload, I suggest:

  1. Create a unique file ID.
  2. Store the image under that ID in the cloud (e.g. S3, Digital Ocean Spaces, etc.).
  3. Use a thumbnailing/CDN service like imgix to extract image metadata.
  4. If the metadata is not valid, delete the S3 file and resolve validation errors.
  5. In your database, record the image under the file ID along with key bits of metadata (user ID, image dimensions, etc.).
  6. Have an Image type. Mine looks like this:

    """
    An object with an ID.
    """
    interface Node {
      """
      The node’s global ID.
      """
      id: ID!
    }
    
    """
    An image.
    """
    type Image implements Node {
      """
      The node’s global ID.
      """
      id: ID!
    
      """
      The image’s MIME type.
      """
      contentType: String!
    
      """
      The image’s file size in bytes.
      """
      fileSize: Int!
    
      """
      The image’s width in pixels.
      """
      width: Int!
    
      """
      The image’s height in pixels.
      """
      height: Int!
    
      """
      The image’s public URL.
      """
      url(
        """
        Image width in pixels.
        """
        width: Int
    
        """
        Image height in pixels.
        """
        height: Int
    
        """
        Image device pixel ratio.
        """
        devicePixelRatio: Float
    
        """
        Image format.
        """
        format: ImageFormat
    
        """
        Compress the image.
        """
        compress: Boolean! = true
    
        """
        Hints for how the image URL should be loaded and cached by the browser, to
        be sent to the client via a
        [`Link`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link)
        header in the GraphQL response.
        """
        resourceHints: ResourceHintsImageInput
      ): String!
    
      """
      The user that uploaded the image.
      """
      user: User
    }
    
    """
    An image format.
    """
    enum ImageFormat {
      WEBP
      PNG
      JPG
      GIF
    }
    
    """
    Hints for how an image URL should be loaded and cached by the browser, to be
    sent to the client via a
    [`Link`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) header
    in the GraphQL response.
    """
    input ResourceHintsImageInput {
      """
      Specifies that the user agent should preemptively fetch and cache the target
      resource as it is likely to be required for a followup navigation.
      """
      prefetch: Boolean
    
      """
      Specifies that the user agent must preemptively fetch and cache the target
      resource for current navigation according to the potential destination given
      by the `as` attribute (and the priority associated with the corresponding destination).
      """
      preload: Boolean
    }

    The url field arguments are used to construct an imgix url for the file ID. If you're lazy you could not have the arguments and just make the url field return the base URL, and document that the user needs to append their own query string for size, etc. Having the sizing, format, etc. customizable via the GraphQL API url field arguments is desirable though because it allows you to switch out the thumbnailing service as an implementation detail, without having to update any app code.