Node.js Demo with Force.com REST API, OAuth & Express
April 27th, 2012
We’ve been working with Node.js quite a bit at CloudSpokes but I hadn’t done anything with Force.com and Node.js using their REST API; we’ve mostly been using our own API. So I thought I would give it a spin and see what it would take to write a small demo app using Node.js, the Force.com REST API, OAuth, Express and Jade for templating.
As it turns out it wasn’t that difficult. Salesforce.com has done most of the work writing the REST and OAuth pieces.
So initially I started woking from Josh Birk‘s FDC-NODEJS-HEROKU repo (don’t use this btw). I should have known from the beginning not to use this as the code hadn’t been updated in 9 months. Anyway a spent some time over the weekend getting it to work with Express, adding some middleware and rewriting some stuff. Once I finally got it working, I emailed Josh with a question and he informed me that the code was outdated and not to you use it.
Being the cool guy that Josh is, he pointed me to Dave Carroll‘s rest4dbdotcom repo for the latest and greatest code. This is definitely the repo you want to watch.
So I put together a small demo that allows you to authorize access to an org, get a list of accounts, create new accounts and update existing ones.
Here’s the link to the live application on heroku and the github repo with the code for your forking pleasure.
Most of the logic for the application is in the main app.js file (below) and you can see that it’s pretty straight forward. We set some configuration parameters for OAuth, configure the middleware, create the server and then define the routes that we can call.
/** * Module dependencies. */var express = require('express') , routes = require('./routes') , rest = require('./rest.js') , oauth = require('./oauth.js') , url = require('url');
/** * Setup some environment variables (heroku) with defaults if not present */var port = process.env.PORT || 3001; // use heroku's dynamic port or 3001 if localhostvar cid = process.env.CLIENT_ID || "YOUR-REMOTE-ACCESS-CONSUMER-KEY";var csecr = process.env.CLIENT_SECRET || "YOUR-REMOTE-ACCESS-CONSUMER-SECRET";var lserv = process.env.LOGIN_SERVER || "https://login.salesforce.com";var redir = process.env.REDIRECT_URI || "http://localhost:" + port + "/token";
/** * Middleware to call identity service and attach result to session */function idcheck() { return function(req, res, next) { // Invoke identity service if we haven't got one or access token has // changed since we got it if (!req.session || !req.session.identity || req.session.identity_check != req.oauth.access_token) { rest.api(req).identity(function(data) { console.log(data); req.session.identity = data; req.session.identity_check = req.oauth.access_token; next(); }); } else { next(); } }}
/** * Create the server */var app = express.createServer( express.cookieParser(), express.session({ secret: csecr }), express.query(), oauth.oauth({ clientId: cid, clientSecret: csecr, loginServer: lserv, redirectUri: redir, }), idcheck());
/** * Configuration the server */app.configure(function(){ app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public'));});
app.configure('development', function(){ app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));});
app.configure('production', function(){ app.use(express.errorHandler());});
/** * Routes */
// 'home' pageapp.get('/', routes.index);
// list of accounts - see routes/index.js for more infoapp.get('/accounts', routes.accounts);
// form to create a new accountapp.get('/accounts/new', function(req, res) { // call describe to dynamically generate the form fields rest.api(req).describe('Account', function(data) { res.render('new', { title: 'New Account', data: data }) });});
// create the account in salesforceapp.post('/accounts/create', function(req, res) { rest.api(req).create("Account", req.body.account, function(results) { if (results.success == true) { res.redirect('/accounts/'+results.id); res.end(); } });});
// display the accountapp.get('/accounts/:id', function(req, res) { rest.api(req).retrieve('Account', req.params.id, null, function(data) { res.render('show', { title: 'Account Details', data: data }); });});
// form to update an existing accountapp.get('/accounts/:id/edit', function(req, res) { rest.api(req).retrieve('Account', req.params.id, null, function(data) { res.render('edit', { title: 'Edit Account', data: data }); });});
// update the account in salesforceapp.post('/accounts/:id/update', function(req, res) { rest.api(req).update("Account", req.params.id, req.body.account, function(results) { res.redirect('/accounts/'+req.params.id); res.end(); }); });
app.listen(port, function(){ console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);});
I did make a small change for the “/accounts” route in app.js. You’ll notice that it delegates to routes/index.js and contains no actual code. When your node application start getting more complex and larger, you’ll want to refactor the code out of app.js and into their own routes to make life easier.
// only needed if calling the rest api from this file (accounts route)var rest = rest = require('./../rest.js');
/* * GET home page. */exports.index = function(req, res){ res.render('index', { title: 'Salesforce.com Node.js REST Demo' })};
/* * GET list of accounts - for larger apps, you may want to separate * code into different routes for ease of maintenance and logic. * Prevents app.js from becoming huge! */exports.accounts = function(req, res){ rest.api(req).query("select id, name from account limit 10", function(data) { res.render("accounts", { title: 'Accounts', data: data, user: req.session.identity } ); });};
Categories: Node.js, Salesforce













[...] a couple of months ago I wrote a Node.js demo with Force.com REST API, OAuth & Express which was a good starter app. However, it uses the web server flow which we typically don’t [...]