Your Dropbox Node.js app is easy

    Despite the fact that my main hobby remains robots, I spend a lot of effort to stay in the trends of my main path - programming. By the will of fate, I recently managed to get acquainted with Node.js, I found out about its express web framework, made friends with the new template engine Jade, and to top it all, I connected all this with a folder in Dropbox.
    image
    In this post I will try to briefly describe how you can organize a web-service for storing files using only free solutions.
    All interested - please, under cat.

    Prepare a foothold


    So we need:
    • Node.js installed on the local machine
    • Dropbox Account
    • Application Node.js server (if you want to start the service not only locally)

    If everything should be clear with the first two points, then I would like to dwell on the third a little more. I have already mentioned that everything should turn out for free, and I'm not going to backtrack on my words.
    In the process of my “floundering” in the Node.js world, I came across a number of platforms ready to put Node.js server at our disposal for free. Personally, I experienced two of them: Heroku and Nodester. As a result, I nevertheless settled on the second, although, frankly, this decision is not substantiated by anything.
    You need a coupon to register with Nodester. You can do this on their website or on the command line via nodester-cli. I received a coupon the day after sending the request. It is very fast, although I do not exclude that I was just lucky.

    Create a project


    Locally

    After all, you have to start with something. To do this, create a folder in any place convenient for us (my name is habr-nodebox) and the package.json file in it:

    {
      "name": "habr-nodebox",
      "version": "0.0.1",
      "node": "0.6.17",
      "author": "andbas",
      "dependencies": {
        "express": "2.5.x",
        "jade": "0.26.x",
        "dbox": "0.4.x"
      }
    }
    

    Fields name, version, author - just give some information about the project and can be changed without any problems; node - the version of Node.js used in the project; The dependencies section lists all used third-party modules. As I mentioned, the project will use express and jade. The dbox plugin, as the name implies, will be used to work with Dropbox. I tried another plugin called dropbox, but it, unfortunately, did not allow authorization of the application, since it implemented the old Dropbox API, which used / token. Dropbox currently uses the oauth standard for authentication.
    After saving this file on the command line, call:

    npm install    
    

    If everything was written correctly, then npm will download all the modules mentioned in dependencies and install them in the current directory.
    In addition, we will create two more folders, public and view. The first will be used for static files (CSS, JS and others), while view will be used for Jade templates.

    Meanwhile in Dropbox

    If we want to be able to store some files in Dropbox, we need to perform several actions, the first of which will be to get the key and secret line for our application. To do this, go to the application page of our Dropbox account through a browser and create a new application there. In the Access type field, set the value to App folder (the application will have limited access only to its own folder in Dropbox).
    On the application page, we write ourselves somewhere the App key and App Secret. Here, in fact, the first step has already been completed.
    To automate the next steps in authorization, I suggest writing a small script on the same node.js. The script is as follows (dbox-init.js in our application folder):

    var dbox  = require("dbox"),
        stdin = process.stdin,
        stdout = process.stdout;
    ask('App key', /^\S+$/, function(app_key) {
      ask('App secret', /^\S+$/, function(app_secret) {
        var app = dbox.app({ 'app_key': app_key, 'app_secret': app_secret });
        app.request_token(function(status, request_token){
          if(request_token){
            console.log('Please visit ', request_token.authorize_url, ' to authorize your app.');
            ask('Is this done? (yes)', /^yes$/, function(answer) {
              app.access_token(request_token, function(status, access_token){
                console.log('app_key: ' + app_key);
                console.log('app_secret: ' + app_secret);
                console.log('oauth_token: ' + access_token.oauth_token);
                console.log('oauth_token_secret: ' + access_token.oauth_token_secret);
                console.log('uid: ' + access_token.uid);
                process.exit();
              });
            });
          }
        });
      });
    });
    function ask(question, format, callback) {
     stdin.resume();
     stdout.write(question + ": ");
     stdin.once('data', function(data) {
       data = data.toString().trim();
       if (format.test(data)) {
         callback(data);
       } else {
         stdout.write("It should match: "+ format +"\n");
         ask(question, format, callback);
       }
     });
    }
    

    In order to run the script, just type in the command line:

    node dbox-init
    

    The script goes through with you all stages of oauth authentication in Dropbox and helps to get all the keys we need. The steps are as follows:
    • the script asks for the App key and App Secret (the ones we got earlier) and generates a link based on them
    • we copy the link to the browser and authorize the application to work with our dropbox account, we receive a notification that the application is authorized
    • to the script’s question about whether we went through authorization, we boldly write “yes”
    • we get and write to a secluded place the data necessary for authorization, namely: app_key, app_secret, oauth_token, oauth_token_secret and uid

    On this preparatory work has been completed, we can proceed to writing the application itself.

    In battle: throw the controller


    We turn, in my opinion, to the most interesting thing - writing a controller. The main actions are: view all files, add new ones, get existing ones. I will not torment and immediately provide the code (web.js).

    var express = require('express'),
        app = express.createServer(express.logger()),
        fs = require('fs'), 
        dropbox  = require("dbox").app({"app_key": process.env['dbox_app_key'], "app_secret": process.env['dbox_app_secret'] }),
        client = dropbox.createClient({oauth_token_secret: process.env['dbox_oauth_token_secret'], oauth_token: process.env['dbox_oauth_token'], uid: process.env['dbox_uid']});
    app.use(express.static(__dirname+'/public'));
    app.set('views', __dirname + '/views');
    app.set('view engine', 'jade');
    app.set('view options', { layout: false });
    app.use(express.bodyParser());
    app.get('/', function(req, res) {
        client.metadata(".", function(status, reply) {
            res.render('index', {
                content : reply
            });
        });
    });
    app.get('/:path', function(req, res) {
        var path = req.params.path;
        client.get(path, function(status, reply, metadata){
          res.send(reply);
        }); 
    });
    app.post('/', function(req, res) {
        var fileMeta = req.files['file-input'];
        if (fileMeta) {
            fs.readFile(fileMeta.path, function(err, data) {
                if (err) throw err;
                client.put(fileMeta.name, data, function(status, reply) {
                    res.redirect('/');
                });
            });
        } else {
            res.redirect('/');
        }
    });
    var port = process.env['app_port'] || 5000;
    app.listen(port, function() {
        console.log("Listening on " + port);
    });
    

    I think a little clarification of what is happening here will not hurt.

    var express = require('express'),
        app = express.createServer(express.logger()),
        fs = require('fs'), 
        dropbox  = require("dbox").app({"app_key": process.env['dbox_app_key'], "app_secret": process.env['dbox_app_secret'] }),
        client = dropbox.createClient({oauth_token_secret: process.env['dbox_oauth_token_secret'], oauth_token: process.env['dbox_oauth_token'], uid: process.env['dbox_uid']});
    

    Announcement of the main bricks of our application:
    • express - as mentioned earlier, web framework
    • app is actually the web application itself
    • fs - file system interface
    • dropbox - factory for creating Dropbox client
    • client - Dropbox client simplifies working with API

    app.use(express.static(__dirname+'/public'));
    app.set('views', __dirname + '/views');
    app.set('view engine', 'jade');
    app.set('view options', { layout: false });
    app.use(express.bodyParser());
    

    Initializing our web application. Here we set the main parameters: paths to static files (public), a directory for templates (views), the engine of these same templates (jade), turn off the main layout to simplify writing a little and get by with one template, and, in the end, pass it to our application bodyParser, which will parse the body of incoming requests.
    Now let's move on to the main magic. Three main handlers of our service will follow.

    app.get('/', function(req, res) {
        client.metadata(".", function(status, reply) {
            res.render('index', {
                content : reply
            });
        });
    });
    

    This code is responsible for the main page. On it we will display a list of files in our Dropbox folder, so we make a request through client and get meta-information. It contains information about all the files in our application. We pass this information to our template engine, which will deal with the rendering of the page.

    app.get('/:path', function(req, res) {
        var path = req.params.path;
        client.get(path, function(status, reply, metadata){
          res.send(reply);
        }); 
    });
    

    The method written above will process requests for receiving files. Everything is extremely simple - we get the file name and send a request to Dropbox. The received response is redirected to the user.

    app.post('/', function(req, res) {
        var fileMeta = req.files['file-input'];
        if (fileMeta) {
            fs.readFile(fileMeta.path, function(err, data) {
                if (err) throw err;
                client.put(fileMeta.name, data, function(status, reply) {
                    res.redirect('/');
                });
            });
        } else {
            res.redirect('/');
        }
    });
    

    Almost all the work for us was done by express. The file sent in the body of the post request to the server was temporarily saved to the file system. We were presented with all the necessary information for us in the req.files ['file-input'] object , where file-input is the name attribute of the input element of the form in html. We only need to take the file from the file system and send it to Dropbox. After that we will be redirected to the main page.

    var port = process.env['app_port'] || 5000;
    app.listen(port, function() {
        console.log("Listening on " + port);
    });
    

    At the end, we set the port for our application, the default value is 5000. It remains only to write one simple page using the jade template, and we will do this.

    Jade template - it's all about indentation


    My first encounter with Jade was personally painful. Normal html is somehow closer.
    However, the discomfort quickly passed. The second page was written without hostility. In general, you get used to it quickly. I recommend to try.
    Discussing the convenience of jade is great, but it's time to show the code. To do this, create an index.jade file in the views folder and write in it:

    !!! 5
    html(lang="en")
      head
        title habr-nodebox 
      body
        each item, i in content.contents
          div
            a(href="#{item.path}") #{item.path} - #{item.size}
        div 
          form#upload-form(action="/", method="POST", enctype="multipart/form-data")
            input#file-input(name="file-input", type="file")
            input#submit(value="Upload file", type="submit")    
    

    I tried to make the template the most transparent and understandable. Of course, achieving outstanding stylistic results will not work, but you will not sacrifice anything for the sake of clarity.
    We just created the simplest HTML page with a list of files in our folder. To do this, we went through the each loop over all the file entries. Let me remind you that the metadata content was carefully received for our template in the controller. Each list item is a link that leads us to the controller method for queries of the form “/: path”. After the list is a form for uploading new files. It consists of two input elements, one for the file, the second for submitting the form.

    Launch


    That's actually our application and it’s ready, it remains only to launch it. Perhaps someone noticed when reading that all the keys to Dropbox were written as variables of the process.env [] array. This is done in order not to leave them in the code and to be able to publish the application without fear of being compromised. In order to pass your values ​​to the process.env array, just write them in the form key = value before calling node. The command line should look something like:
    $ dbox_app_key=abc1qwe2rty3asd dbox_app_secret=123asd123asd123 dbox_oauth_token_secret=aaabbbccc111222 dbox_oauth_token=123asd123asd123 dbox_uid=12345678 node web 
    

    On services such as Nodester, it’s possible to install process.env, so this was the only way without any problems that came to my mind.
    The result, after adding several files, will look as follows.


    That's all, if someone wants to run such code online - just upload it to any Node.js server. I tested on Nodester - everything worked, I think there will be no problems with the rest.
    The code can be found here.

    Also popular now: