{"id":15627092,"url":"https://github.com/adelowo/gulter","last_synced_at":"2025-09-01T20:39:40.179Z","repository":{"id":222574981,"uuid":"757161737","full_name":"adelowo/gulter","owner":"adelowo","description":"Golang middleware for handling multipart/form-data and uploading files","archived":false,"fork":false,"pushed_at":"2024-09-21T15:35:14.000Z","size":149,"stargazers_count":61,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-31T17:37:28.809Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adelowo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-13T23:17:20.000Z","updated_at":"2024-12-24T07:44:22.000Z","dependencies_parsed_at":"2024-02-29T01:23:29.755Z","dependency_job_id":"fe0f01ea-0eef-4435-b5bd-63591aaae2ae","html_url":"https://github.com/adelowo/gulter","commit_stats":null,"previous_names":["adelowo/gulter"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adelowo%2Fgulter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adelowo%2Fgulter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adelowo%2Fgulter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adelowo%2Fgulter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adelowo","download_url":"https://codeload.github.com/adelowo/gulter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232292878,"owners_count":18500681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T10:15:19.981Z","updated_at":"2025-01-03T05:19:32.063Z","avatar_url":"https://github.com/adelowo.png","language":"Go","funding_links":[],"categories":["File Handling","Recently Updated","文件处理"],"sub_categories":["Search and Analytic Databases","[Dec 24, 2024](/content/2024/12/24/README.md)","检索及分析资料库"],"readme":"# Gulter\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/adelowo/gulter.svg)](https://pkg.go.dev/github.com/adelowo/gulter)\n[![Go Report Card](https://goreportcard.com/badge/github.com/adelowo/gulter)](https://goreportcard.com/report/github.com/adelowo/gulter)\n\nGulter is a Go HTTP middleware designed to simplify the process of uploading files\nfor your web apps. It follows the standard\n`http.Handler` and `http.HandlerFunc` interfaces so you can\nalways use with any of framework or the standard library router.\n\n\u003e [!NOTE]\n\u003e Name and idea was gotten from the insanely popular multer package in NodeJS that does the same\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Standard HTTP router](#standard-http-router)\n  - [Chi Router and others](#chi-router-and-other-compatible-http-handlers)\n- [API](#api)\n- [FAQs](#faqs)\n  - [customizing http error](#customizing-the-error-response)\n  - [ignoring keys](#ignoring-non-existent-keys-in-the-multipart-request)\n  - [custom validation logic](#writing-your-custom-validator-logic)\n\n## Installation\n\n```sh\n\ngo get -u -v github.com/adelowo/gulter\n\n```\n\n## Usage\n\nAssuming you have a HTML form like this:\n\n```html\n\u003cform action=\"/\" method=\"post\" enctype=\"multipart/form-data\"\u003e\n  \u003cinput type=\"file\" name=\"form-field-1\" /\u003e\n  \u003cinput type=\"file\" name=\"form-field-2\" /\u003e\n\u003c/form\u003e\n```\n\nTo create a new Gulter instance, you can do something like this:\n\n```go\n handler, _ := gulter.New(\n  gulter.WithMaxFileSize(10\u003c\u003c20),\n  gulter.WithValidationFunc(\n   gulter.ChainValidators(gulter.MimeTypeValidator(\"image/jpeg\", \"image/png\"),\n    func(f gulter.File) error {\n     // Your own custom validation function on the file here\n     // Else you can really just drop the ChainValidators and use only the MimeTypeValidator or just\n     // one custom validator alone\n     return nil\n    })),\n  gulter.WithStorage(s3Store),\n )\n```\n\nThe `handler` is really just a HTTP middleware with the following signature\n`Upload(keys ...string) func(next http.Handler) http.Handler`. `keys` here\nare the input names from the HTML form, so you can chain this into almost any HTTP\nrouter.\n\n### Standard HTTP router\n\n```go\npackage main\n\nimport (\n \"fmt\"\n \"net/http\"\n\n \"github.com/adelowo/gulter\"\n \"github.com/adelowo/gulter/storage\"\n)\n\nfunc main() {\n s3Store, err := storage.NewS3FromEnvironment(storage.S3Options{\n  Bucket: \"std-router\",\n })\n if err != nil {\n  panic(err.Error())\n }\n\n handler, err := gulter.New(\n  gulter.WithMaxFileSize(10\u003c\u003c20),\n  gulter.WithStorage(s3Store),\n )\n\n mux := http.NewServeMux()\n\n // upload all files with the \"name\" and \"lanre\" fields on this route\n mux.Handle(\"/\", handler.Upload(\"name\", \"lanre\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n  fmt.Println(\"Uploaded file\")\n\n  // return all uploaded files\n  f, err := gulter.FilesFromContext(r)\n  if err != nil {\n   fmt.Println(err)\n   return\n  }\n\n  // return uploaded files with the form field \"lanre\"\n  ff, err := gulter.FilesFromContextWithKey(r, \"lanre\")\n  if err != nil {\n   fmt.Println(err)\n   return\n  }\n\n  fmt.Printf(\"%+v\", ff)\n\n  for _, v := range f {\n   fmt.Printf(\"%+v\", v)\n   fmt.Println()\n  }\n })))\n\n http.ListenAndServe(\":3300\", mux)\n\n```\n\n### Chi router and other compatible HTTP handlers\n\n```go\n\n s3Store, err := storage.NewS3FromEnvironment(storage.S3Options{\n  Bucket: \"chi-router\",\n })\n if err != nil {\n  panic(err.Error())\n }\n\n handler := gulter.New(\n  gulter.WithMaxFileSize(10\u003c\u003c20),\n  gulter.WithValidationFunc(gulter.ChainValidators(gulter.MimeTypeValidator(\"image/jpeg\", \"image/png\"))),\n  gulter.WithStorage(s3Store),\n )\n\n router := chi.NewMux()\n\n  // upload all files in the form fields called \"form-field-1\" and \"form-field-2\"\n router.With(handler.Upload(\"form-field-1\", \"form-field-2\")).Post(\"/\", func(w http.ResponseWriter, r *http.Request) {\n  fmt.Println(\"Uploaded file\")\n\n  f, err := gulter.FilesFromContext(r)\n  if err != nil {\n   fmt.Println(err)\n   return\n  }\n\n  ff, err := gulter.FilesFromContextWithKey(r, \"form-field-1\") // or form-field-2\n  if err != nil {\n   fmt.Println(err)\n   return\n  }\n\n  fmt.Printf(\"%+v\", ff)\n\n  for _, v := range f {\n   fmt.Printf(\"%+v\", v)\n   fmt.Println()\n  }\n })\n\n```\n\n## API\n\nWhile this middleware automatically uploads your files, sometimes you need\ndetails about the uploaded file to show to the user, this could be making up the\nimage url or path to the image. To get that in your HTTP handler, you can use either:\n\n- `FilesFromContextWithKey`: retrieve a named input uploaded file.\n- `FilesFromContext`: retrieve all uploaded files\n\nGulter also ships with two storage implementations at the moment:\n\n- `S3Store` : supports S3 or any compatible service like Minio,R2 \u0026 others\n- `DiskStore`: uses a local filesystem backed store to upload files\n- `CloudinaryStore`: uploads file to cloudinary\n\n## FAQs\n\n### Ignoring non existent keys in the multipart Request\n\nSometimes, the keys you have configured the middleware might get dropped from the\nfrontend for some reason, ideally the middleware fails if it cannot find a\nconfigured key in the request. To disable this behavior and ignore the missing\nkey, you can make use of the `WithIgnoreNonExistentKey(true)` option to prevent the\nmiddleware from causing an error when such keys do not exists\n\n### Customizing the error response\n\nSince Gulter is a middleware that runs, it returns an error to the client if found,\nthis might not match your existing structure, so to configure the response, use the\n`WithErrorResponseHandler`. The default is shown below and can be used as a template\nto define yours.\n\n```go\n\n errHandler ErrResponseHandler = func(err error) http.HandlerFunc {\n  return func(w http.ResponseWriter, _ *http.Request) {\n   w.Header().Set(\"Content-Type\", \"application/json\")\n   w.WriteHeader(http.StatusInternalServerError)\n   fmt.Fprintf(w, `{\"message\" : \"could not upload file\", \"error\" : %s}`, err.Error())\n  }\n }\n```\n\n### Writing your custom validator logic\n\nSometimes, you could have some custom logic to validate uploads, in this example\nbelow, we limit the size of the upload based on the mimeypes of the uploaded files\n\n```go\n\nvar customValidator gulter.ValidationFunc = func(f gulter.File) error {\n switch f.MimeType {\n case \"image/png\":\n  if f.Size \u003e 4096 {\n   return errors.New(\"file size too large\")\n  }\n\n  return nil\n\n case \"application/pdf\":\n  if f.Size \u003e (1024 * 10) {\n   return errors.New(\"file size too large\")\n  }\n\n  return nil\n default:\n  return nil\n }\n}\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadelowo%2Fgulter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadelowo%2Fgulter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadelowo%2Fgulter/lists"}