Uploading a File to Amazon S3 Using Clojure and Noir

Before we start

To upload files to Amazon S3 we need, surprise!, an Amazon S3 account.
Go to the S3 homepage and signup, note that you need a credit card to do this.

From the S3 console make a new bucket.

What we will use

For this tutorial we will use:

  • Clojure 1.2.1
  • Noir 1.1.0

Project setup

Create a new noir project, if you don't have noir installed install it

lein noir new file-uploadr

Remove src/file_uploadr/views/welcome.clj, put this code in src/file_uploadr/views/upload.clj

(ns file-uploadr.views.upload
  (:require [file-uploadr.views.common :as common]
            [noir.content.pages :as pages]
            [noir.response :as resp]
            [noir.util.s3 :as s3])
  (:use noir.core
(defpage "/" []
           [:p "Welcome to file-uploadr"]))

Then start the web server with

lein run

and open it at http://localhost:8080/.

The form

Now we can write our page with the form. Change the definition of your "/" page to look like this

(defpage "/" []
          (form-to {:enctype "multipart/form-data"}
                   [:post "/upload"]
                   (label :file "File to upload:")
                   (file-upload :file)
                   (submit-button "Upload"))))

Notice that we had to add {:enctype "multipart/form-data"} to the form.

Play with the REPL

The form is ready but first we need to learn how to interact to Amazon S3 using Clojure, so start your REPL! M-x clojure-jack-in

Start by requiring the S3 util in noir

(require '[noir.util.s3 :as s3])
;=> nil

Create a map containing your S3 credentials

(def *server-spec* {:secret-key "SECRET KEY"
                    :access-key "ACCESS KEY" })
;=> #'user/*server-spec*

and a sample file

(def project-file (java.io.File. "project.clj"))
;=> #'user/project-file

Now we can upload our first file to S3!

(binding [s3/*s3* (s3/service *server-spec*)]
 (s3/put! "s3-tut" project-file))
;=> #<S3Object S3Object [key=project.clj, ...]>

You can find your file at https://s3.amazonaws.com/<bucket-name>/project.clj, the general URL is (http|https)://s3.amazonaws.com/<bucket-name>/<file-name>.
Now that we know how to upload a file we can finish our application.

Finishing the application

Add the following code to upload.clj be able to upload a file to S3

; Show this page in case of success
(defpage "/success" []
   [:h2 "File uploaded!"]))
; Show this page in case of failure
(defpage "/fail" []
   [:h2 "Something went wrong"]))
;; S3 keys - replace with your keys
(def *server-spec* {:secret-key "SECRET KEY" :access-key "ACCESS KEY"})
(defpage [:post "/upload"] {:keys [file]}
  (if (= "0" (:size file))
      ;; Upload the file to the "s3-tut" bucket
      (binding [s3/*s3* (s3/service *server-spec*)]
        (s3/put! "s3-tut" (:tempfile file)))
      (resp/redirect "/success"))
    (resp/redirect "/fail")))

I use the (binding [s3/*s3* (s3/service *server-spec*)] …) because it seems there's a bug in the with-s3 macro.

Refresh the page, upload a file, then go to your S3 console, you should see it here!

What needs improvements

The file you have uploaded has an ugly/random name and it's doesn't have the correct MIME type.
This can be annoying depending on what you plan to do with the files you upload.

  • http://twitter.com/fangyixun Eric

    Hi, the file just seems to be a string of the title of the file that was uploaded and not the file itself. How do I get the file?
    Do I need to add some ring middleware to do that?

  • http://twitter.com/fangyixun Eric

    Oh sorry, it's my fault. Mistype 
    enctype  to encrypt.

  • http://twitter.com/elliottwilliams elliott williams

    i think you mean (if (= "0" (:size file))

  • http://twitter.com/elliottwilliams elliott williams

    or rather (not (if (= "0" (:size file)))