Here we will learn how to upload, retrieve, update, and delete files using the Go Gin framework and Cloudinary. Cloudinary makes handling files of all types of images, videos, and documents easy so you will have more time for logic. Gin is an easy framework to develop web applications and APIs which we will be using to help develop our app.
You will need to add the following dependencies and install Go to you machine
Go: You can install Go easily from the Go website if you haven’t installed it already
Visual Studio Code: This is a preferred IDE since it has nice autocompletion and Go extensions for building Go applications. You can still use GoLand which is a paid IDE
Cloudinary Account: You will need a Cloudinary API key to upload files to their service
Before we begin uploading files to the cloud let’s first start by understanding how we can upload locally for access. You can start by opening Visual Studio Code and initializing your app with go mod init github.com/{username}/uploadingfiles. Replace the username with your name. Initializing your app will help manage your app’s dependencies.
Before adding any dependencies we will use Go’s powerful inbuilt http library to handle requests and file uploads
We can now initialize our app by creating a main.go file in the root folder of our app. We will start by setting up our server using http. After running go run main.go from your terminal you will get the message as printed, adding functionality to our upload function. This will allow us to upload files to a temporary destination.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func uploadFile(w http.ResponseWriter, r * http.Request) {
// Ensure our file does not exceed 5MB
r.Body = http.MaxBytesReader(w, r.Body, 5 * 1024 * 1024)
file, handler, err: = r.FormFile("file")
// Capture any errors that may arise
if err != nil {
fmt.Fprintf(w, "Error getting the file")
fmt.Println(err)
return
}
defer file.Close()
fmt.Printf("Uploaded file name: %+v\n", handler.Filename)
fmt.Printf("Uploaded file size %+v\n", handler.Size)
fmt.Printf("File mime type %+v\n", handler.Header)
// Get the file content type and access the file extension
fileType: = strings.Split(handler.Header.Get("Content-Type"), "/")[1]
// Create the temporary file name
fileName: = fmt.Sprintf("upload-*.%s", fileType)
// Create a temporary file with a dir folder
tempFile, err: = ioutil.TempFile("temp-files", fileName)
if err != nil {
fmt.Println(err)
}
defer tempFile.Close()
fileBytes, err: = ioutil.ReadAll(file)
if err != nil {
fmt.Println(err)
}
tempFile.Write(fileBytes)
fmt.Fprintf(w, "Successfully uploaded file")
}
Saving files has a couple of disadvantages such as:
Simple functionalities such as creating a file need to have extra validations to ensure we are getting the right file we asked for.
Asset Administration: Accessing files after creating them becomes a bit tedious. Meta fields about the field become very hard to access. Deleting, updating the filename, and other metadata also becomes very hard.
Hosting services problem: You will have a problem managing deleted files after you deploy it. Services such as Heroku don’t allow uploading files to a project’s directory.
Cloudinary fixes some of these features by providing a better way to save files, update a file, delete it, and modify it very easily through its available SDK.
You can set up Cloudinary quite easily. Head over to Cloudinary and create a free account. After creating an account you’ll be redirected to the dashboard; on the top part you will find your API keys. We will use the API keys to validate the Cloudinary in our application. After creating an account we will add the following dependencies:
Gin: A Simple Go web framework to help in creating our APIs
Cloudinary-Go: Cloudinary SDK for Golang
Add Gin using the following command
$ go get -u github.com/gin-gonic/gin
Add the Cloudinary dependency using this command
$ go get github.com/cloudinary/cloudinary-go
Updating our functions to use Gin. You can create the following functions so that we use the Gin framework. Here’s what our changes look like.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import "github.com/gin-gonic/gin"
func uploadFile(c * gin.Context) {}
func getUploadedFiles(c * gin.Context) {}
func updateFile(c * gin.Context) {}
func deleteFile(c * gin.Context) {}
func home(c * gin.Context) {
c.JSON(200, gin.H {
"message": "You are at home",
})
}
func routes() {
router: = gin.Default()
router.GET("/", home)
router.POST("/upload", uploadFile)
router.POST("/get-files", getAllUploadedAssets)
router.GET("/get-upload/:assetId", getUploadedFile)
router.PUT("/update-file/:name", updateFile)
router.DELETE("/delete-file/:assetId", deleteAsset)
// Default port is 8080 you can change it to any port you like
router.Run()
}
func main() {
routes()
}
We can now update our upload function so that we are able to use Cloudinary. To be able to access its APIs we need to verify ourselves. Head to the Cloudinary dashboard in the account you’ve just created and get the Cloudname, ApiKey, and the Secret. You will find these in the dashboard account details. You can also validate using the defined URL stated there as an API environment variable.
Before uploading a file you need an instance of Cloudinary with the credentials set. We will create an instance.
1 2
// Create our instance cld, _: = cloudinary.NewFromURL("add_your_url_here")
With Cloudinary we can easily manipulate the data such as the public id, which is the preferred filename. Now that we are authorized we can set up our upload function endpoint. Cloudinary provides more ways to manipulate how we save our files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
// Get the preferred name of the file if its not supplied fileName: = c.PostForm("name") // Add tags fileTags: = c.PostForm("tags") file, _, err: = c.Request.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H { "error": err, "message": "Failed to upload", }) } result, err: = cld.Upload.Upload(c, file, uploader.UploadParams { PublicID: fileName, // Split the tags by comma Tags: strings.Split(",", fileTags), }) if err != nil { c.String(http.StatusConflict, "Upload to cloudinary failed") } c.JSON(http.StatusCreated, gin.H { "message": "Successfully uploaded the file", "secureURL": result.SecureURL, "publicURL": result.URL, }) // Other code...
Go’s Gin framework allows us to access any file provided by using c.PostForm we can get extra details to add. Uploading to Cloudinary uses three parameters: the context, the file and the upload params, in this case the extra details. Here are some properties you can manipulate about your file.
PublicId: This is your preferred label or visible name for the file.
Tags: For cases in which you want an album you can add a tag to help categorize the files better.
CreatedAt: You can define the date the file was created, otherwise it falls back to the date it was uploaded. This is great because it helps manage and access similar files or files that have similar content better.
There is a lot of other data you can change depending on the current project. Here’s a link you can refer to. On sending our data with Postman we get a secure url and a public url. The secure url uses https while the other one does not.
Accessing Files
Accessing the files stored in Cloudinary is very simple; you only need to pass the filename you gave it or the unique id, AccessId, that each file has. You can also use its powerful query search function**. **
Single File Access
To access a single file you can use the file name or use the AccessId to ensure you get only one file (in case there are two files with the same name). In this case we are getting the file name from the url param. With Gin you provide the param using the following format.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
router.GET("/get-upload/:assetId", getUploadedFile)
func getUploadedFile(c * gin.Context) {
// Create our instance
cld,
_: = cloudinary.NewFromURL("your_cloud_url")
fileName: = c.Param("accessId")
// Access the filename using a desired file access id.
result,
err: = cld.Admin.Asset(c, admin.AssetParams {
AccessID: fileName
})
if err != nil {
c.String(http.StatusNotFound, "We were unable to find the file requested")
}
c.JSON(http.StatusAccepted, gin.H {
"message": "Successfully found the image",
"secureURL": result.SecureURL,
"publicURL": result.URL,
"created_at": result.CreatedAt.String(),
})
}
Multiple File Access
You can access some of the files using the predefined search API.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cld, _: = cloudinary.NewFromURL("your_cloud_url")
var urls[] string
searchQ: = search.Query {
Expression: "resource_type:image AND uploaded_at>1d AND bytes<1m",
SortBy: [] search.SortByField {
{
"created_at": search.Descending
}
},
MaxResults: 10,
}
results, err: = cld.Admin.Search(c, searchQ)
if err != nil {
c.JSON(http.StatusNotFound, gin.H {
"error": err,
"message": "Failed to query your files",
})
}
for _, asset: = range results.Assets {
urls = append(urls, asset.SecureURL)
}
c.JSON(http.StatusAccepted, gin.H {
"success": true,
"data": urls,
})
}
Asset management in Cloudinary allows us to either update a current file or delete it.
Updating a file
You can easily update the file’s tags, public id, or name. You can also manipulate the photo ratio, the height, and width, among other details. Upload.Rename only works for public id which permanently changes the public id.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func updateFile(c * gin.Context) {
cld,
_: = cloudinary.NewFromURL("your_url")
fileId: = c.Param("publicId")
newFileName: = c.PostForm("fileName")
// Access the filename using a desired filename
result,
err: = cld.Upload.Rename(c, uploader.RenameParams {
FromPublicID: fileId,
ToPublicID: newFileName,
})
if err != nil {
c.String(http.StatusNotFound, "We were unable to find the file requested")
}
c.JSON(http.StatusAccepted, gin.H {
"message": "Successfully found the image",
"secureURL": result.SecureURL,
"publicURL": result.URL,
"created_at": result.CreatedAt.String(),
})
}
Deleting A file
You can delete a file in the cloud using Destroy. Here’s how it works.
1 2 3 4 5 6 7 8 9 10 11
cld, _: = cloudinary.NewFromURL("cloudinary://775819266531562:0vvHyxdwaqAHBjT44neW6h572HY@djmi67ke1") fileId: = c.Param("assetId") result, err: = cld.Upload.Destroy(c, uploader.DestroyParams { PublicID: fileId }) if err != nil { c.String(http.StatusBadRequest, "File could not be deleted") } c.JSON(http.StatusContinue, result.Result)
Managing your files with Go and Cloudinary is quite simple and removes the overhead of continuously figuring out how to manage locally stored files. In the next post we will learn how to create our frontend with React which will consume our data.
You can find the repo here for all the source code.