Tutorial: How to implement Image-Upload

Martin Lasek
Jan 29, 2018 · 10 min read

Be excited! Because at the end of this tutorial you’ll know how to upload an image for a user and save it to the disk as well as how to serve it back as a profile picture ✨ !

You can find the result of this tutorial on github here

This tutorial is a natural follow-up of How to write Controllers. You can either go for that tutorial first and come back later or be a rebel, skip it and read on 😊


Index

1. Create and generate a new project
2. Adjust Model: User
3. Create View: Upload
4. Adjust UserController: Add Upload-Routes
5. Create View: Profile
6. Adjust UserController: Add Profile-Route
7. Adjust UserController: Add Image-Route


1. Create and generate a new project

We will use the outcome of the aforementioned tutorial as a template to create our new project:

vapor new projectName --template=vaporberlin/my-first-controller --branch=vapor-2

Before we generate an Xcode project we would have to change the package name within Package.swift:

// swift-tools-version:4.0

Now in the terminal at the root directory projectName/ execute:

vapor update -y

NOTE:
Sometimes you’d get an error like “backgroundExecute(code: 1, error..”
No worries - execute the command again, it sometime takes multiple attempts 😊

It may take a bit fetching the dependency, but when done you should have a project structure like this:

projectName/
├── Package.swift
├── Sources/
│ ├── App/
│ │ ├── Controllers/
│ │ │ └── UserController.swift
│ │ ├── Models/
│ │ │ └── User.swift
│ │ ├── Routes/
│ │ │ └── Routes.swift
│ │ └── Setup/
│ │ ├── Config+Setup.swift
│ │ └── Droplet+Setup.swift
│ └── Run/
├── Tests/
├── Config/
├── Resources/
├── Public/
├── Dependencies/
└── Products/

2. Adjust Model: User

The first thing we’re going to do is give our user a new property to be able to store the name to his profile image.

import Vapor
import FluentProvider

That was easy and you may have seen / learned how to define optional database fields for the first time 😊


3. Create View: Upload

Let’s implement a view with a form that has a dropdown to select a user and a button to select an image to upload it for the selected user! So within Resources/Views/ create a new file with the name upload.leaf and add:

<!DOCTYPE html>
<html>
<head>
<title>Image-Upload</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">

I have marked the important part here. I think the most interesting element is the input tag of type file. That is our button which allows us to select images from our disk. With accept we’re restricting the selection to .jpg and .png only. You can leave it empty in order to allow all files or use application/pdf for pdf only. Every element around the form are used to get a nicer look by using bootstrap classes 😊


4. Adjust UserController: Add Upload-Routes

The first route for returning our upload view is simple. Add the following within our Controllers/UserController.swift:

final class UserController {
...

And then go to Routes/Routes.swift and add the new route:

import Vapor

If you now cmd+r or run everything should built without any error and we should see our nice upload-form when opening /upload in our browser 😊 !
(The dropdown will be empty because you didn’t create a user under /user)

Note: make sure to select Run as a scheme next to your button before running the app

The next route we need to create is the post-route that will handle the data of our form when we submit it. This is the core of this tutorial so I will explain the code right afterwards. Within Controllers/UserController.swift add:

import Foundation

Now let’s have a closer look. We added a new import. Yap! Easy to oversee but Foundation is needed 😊. Next to our function. The first guard-let statement tries to get all the information from the form we require for further operation. So we grab the userId and try to find our user by that. Next we grab the image bytes and also the filename. I love how easy it actually is!

We will store all our images within projectName/images/ in this tutorial, go and create that directory right now so we don’t forget it later 😊

Fortunately drop.config.workDir gives us the directory path of our project as a string so we just append “images” as another pathComponent. I thought it would be nice to create for each user a directory named after his username (which we can ensure to be unique in the create-user function) in order to keep things sorted and organized. That’s why we append the username as another pathComponent to our baseDir and store it in new variable userDir.

FileManager is a wonderful thing! We check whether a directory exists under the userDir path and if not, we create one.

NOTE: Since we work with an URL object - some functions of our FileManager accept that, some of them need a string. That’s why we use .path in some cases.

Next thing we append the filename to our userDir and store it in another variable userDirWithImage. The path will look similar to something like:

/Users/martinlasek/projects/projectName/images/harrypotter/wand.jpg

Here comes the magic. Next we initiate a Data-object with our image bytes. Then our FileManager creates that file at the given path we pass into with the data with also pass into. To me when I first saw a directory and a file created by a software I wrote I was amazed. It was a fantastic feeling! ✨

Okay so finally we assign the filename to our users property profileImage and save him to the database before redirecting to a route, yet to be created: /user/yourCoolUsername

Let’s add the route in Routes/Routes.swift that uses our postUpload:

import Vapor

If you now cmd+r or run your project and go to /user to create a new user and then go to /upload to select this user and then select a picture and hit upload - you can go to your project with finder and look what happened inside your image/ directory ✨


5. Create View: Profile

Now it’s time to create a view in which we can see the profile image of a user! So within Resources/Views/ create a new file, name it profile.leaf and add:

<!DOCTYPE html>
<html>
<head>
<title>Profile</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container">
<h1> Profile of #(user.username)</h1>
<div class="row">
<div class="col-xs-6 col-md-3">
<div class="thumbnail">
#if(hasImage) {
<img src="/profile-image/#(user.username)">
} ##else() {
<p> no image given for #(user.username)</p>
}
</div>
</div>
</div>

</body>
</html>

We are checking whether the variable hasImage that we will pass into the view in the next step is true and if so then please render an image from the source “/profile-image/myCoolUsername” which is a route that we will implement in step 7!


6. Adjust UserController: Add Profile-Route

All we need is a route returning the profile-view ultimately passing in some information. Therefor within our Controllers/UserController.swift add:

import Foundation

Let’s head right over to our Routes/Routes.swift and add that route:

import Vapor

Our function is grabbing the username from the url and trying to find a user by it. Then it stores the check whether the users profileImage is nil or not in hasImage, passes it with the user into our profile-view and returns the view.

If you now hit cmd+r or run and create a user under user/ and upload an image under upload/ you’ll be redirected to user/myCoolUsername/ where you won’t see an image, yet - let’s change that in the next step!


7. Adjust UserController: Add Image-Route

Here we are. Only you and me. And the code to be written to return a users image. Okay so within our Controllers/UserController.swift add:

import Foundation

What we do here is we once again grab the username from the url and try to find a user by it and then check whether he has a profileImage. If yes we can build the path to that image since we now how we organized our images and have it super simple to return it since vapor provides the right Response for it! Now to add the last route within out Routes/Routes.swift:

import Vapor

A final cmd+r or run and that’s it! Create a new user under user/ and go to upload/ and upload a new image and you’re done! Yey! You have successfully implemented an image-upload 🌆✨ !!


Thank you a lot for reading! If you have any questions or improvements — write a comment! I would love to hear from you! 😊

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store