History: Photo sharing service

Revision made 8 years ago by Francisco Presencia. Go to the last revision.

We are going to create a photo sharing service where the user is able to upload pictures and like them.

We will be using a simple MVC, so our file structure will be:

/controllers
  /photos.js
/models
  /photos.js
/views
  /index.pug
  /layout.pug
/public
  /images
    /1.jpg
    /2.jpg
    ...
  /style.css
/.env
/.gitignore
/app.js
/package.json
/Procfile
/routes.js

So far there will be no user auth as it'd take too long to explain it all. We will need to install few packages within our web folder, which will be explained as we go:

npm install auto-load body-parser cloudinary dotenv express formidable mongoose pug --save

A small reminder about requests flow; when the user requests any page in any way, it will reach the app.js. In that file we will have all of our server initial setup, and then the flow will continue with our router. From the router we specify what call goes where.

So we first set up our app.js:

// Load the environment configuration from ".env"
require('dotenv').config();

// Import the modules that we will use in this file
var express = require('express');
var mongoose = require('mongoose');
var routes = require('./routes');

// Server configuration
var app = express();
app.set('view engine', 'pug');  // The "html engine"
app.use(express.static('public'));  // Where the static files are

// Connect to the database before accepting any request
mongoose.connect('mongodb://localhost/test');
var db = mongoose.connection;
db.once('open', function(){

  // Use the routes stored in ./routes.js
  app.use(routes);
 
  // Actually start listening to the requests
  app.listen(process.env.PORT || 3000);
});

In routes.js:

var express = require('express');
var router = express.Router();
var controllers = require('auto-load')('controllers');

router.get('/', controllers.photos.index);

module.exports = router;

Let's mock the data:

module.exports.index = function(req, res){
  res.render('index', {
    photos: [
      '/images/1.jpg',
      '/images/2.jpg',
      '/images/3.jpg',
      '/images/4.jpg',
      '/images/5.jpg'
    ]
  });
}

Install the dependencies

npm install formidable cloudinary --save

Then in our controllers/photos.js:

var formidable = require('formidable');
var cloudinary = require('cloudinary');

cloudinary.config({
  cloud_name: process.env.cloud,
  api_key: process.env.key,
  api_secret: process.env.secret
});

exports.upload = function(req, res, next){
  var form = new formidable.IncomingForm();
  form.parse(req, function (err, fields, files) {
    if (err) next(err);
    cloudinary.uploader.upload(files.image.path, function(result) {
      // Let's force https in the image:
      var image = result.url.replace('http://', 'https://');
      res.redirect('/');
    });
  });
});

Our layout.pug:

doctype html
html
  head
    title= title
    link(rel="stylesheet" href="https://cdn.jsdelivr.net/picnicss/6.0.0/plugins.min.css")
    link(rel='stylesheet', href='style.css')
    block head
  body
    block content

    script.
      document.addEventListener("DOMContentLoaded", function() {
        [].forEach.call(document.querySelectorAll('.dropimage'), function(img){
          img.onchange = function(e){
            var inputfile = this, reader = new FileReader();
            reader.onloadend = function(){
              inputfile.style['background-image'] = 'url('+reader.result+')';
            }
            reader.readAsDataURL(e.target.files[0]);
          }
        });
      });

In index.pug:

extends layout

block content

  h1= name

  div.flex.one.two-500.five-900
    each photo in photos
      div.photo
        img(src=photo)
        button.like Like

  form(action="/photos" method="POST" enctype="multipart/form-data")
    div.flex.two
      div
        label.dropimage
          input(type="file" name="image" title="Drop image or click me")
      div
        label Title:
          input(name="title" placeholder="Title")
        label Nickname:
          input(name="nick" placeholder="Nickname")
        input(type="submit" value="Upload")

In our style.css:

body {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  padding: 20px;
}

img {
  width: 100%;
}

New photos.js:

var models = require('auto-load')('models');

var formidable = require('formidable');
var cloudinary = require('cloudinary');

cloudinary.config({
  cloud_name: process.env.cloud,
  api_key: process.env.key,
  api_secret: process.env.secret
});

module.exports.index = function(req, res){
  models.photos.find({}, function(err, images){
    res.render('index', { photos: images });
  });
}

module.exports.upload = function(req, res, next){
  var form = new formidable.IncomingForm();

  function redirect(err){
    if (err) next(err);
    res.redirect('/');
  }

  function parseForm(err, fields, files) {
    if (err) next(err);
    cloudinary.uploader.upload(files.image.path, function (result) {
      // Let's force https in the image:
      var url = result.url.replace('http://', 'https://');

      var Photo = new models.photos({
        url: url,
        title: fields.title,
        nickname: fields.nick,
      });
      Photo.save(redirect);
    });
  }

  form.parse(req, parseForm);
}

The model.js:

var mongoose = require('mongoose');

var photosSchema = mongoose.Schema({
  title: { type: String, required: true },
  nickname: { type: String, unique: true, required: true },
  url: { type: String, required: true },
  likes: [{ type: String }]
});

module.exports = mongoose.model('Photos', photosSchema);

Auth

We will follow Auth0 tutorial.