├── .gitignore
├── Procfile
├── README.md
├── app.json
├── package.json
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | .DS_Store
4 | /.idea/
5 | /npm-debug.log
6 | /.atom/
7 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm start
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Salesforce ETL MySQL
2 | --------------------
3 |
4 | This sample application shows a simple way to use a Node.js app along with Workflow on Salesforce to do Extract, Transform, and Load (ETL) from Salesforce to MySQL.
5 |
6 | ### Run on Heroku
7 | 1. Deploy the app: [](https://heroku.com/deploy)
8 |
9 |
10 | ### Run Locally
11 |
12 | 1. Create a local MySQL database named `demo`
13 | 1. Install the Node.js dependencies:
14 |
15 | npm install
16 |
17 | 1. Run the local dev server:
18 |
19 | npm run dev
20 |
21 | 1. Start an [ngrok](https://ngrok.com/) tunnel:
22 |
23 | ngrok http 5000
24 |
25 |
26 | ### Setup a Salesforce Workflow & Outbound Message
27 |
28 | 1. [Create a new Workflow](https://login.salesforce.com/01Q)
29 | 1. Select the `Contact` object
30 | 1. Give the rule a name
31 | 1. Select `created, and every time it's edited`
32 | 1. In the `Rule Critera` select `forumla evaluates to true` and enter `True` in the formula field
33 | 1. In `Immediate Workflow Actions` select `New Outbound Message`
34 | 1. Give the Outbound Message a name, enter the `Endpoint URL` for either your Heroku app (e.g. `https://foo.herokuapp.com/`) or your ngrok endpoint for local testing (e.g. `https://1234.ngrok.io/`)
35 | 1. Select the `Email`, `FirstName`, and `LastName` fields
36 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Salesforce ETL MySQL",
3 | "description": "Sample Salesforce ETL to MySQL",
4 | "repository": "https://github.com/jamesward/salesforce-etl-mysql",
5 | "website": "http://www.jamesward.com",
6 | "keywords": ["node", "salesforce", "etl", "mysql"],
7 | "addons": ["cleardb:ignite"]
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "salesforce-etl-mysql",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev": "node node_modules/nodemon/bin/nodemon.js server.js",
6 | "start": "node server.js"
7 | },
8 | "engines": {
9 | "node": "6.11.1"
10 | },
11 | "devDependencies": {
12 | "nodemon": "latest",
13 | "npm-check-updates": "latest"
14 | },
15 | "dependencies": {
16 | "express": "4.14.0",
17 | "express-xml-bodyparser": "0.3.0",
18 | "mysql": "2.11.1"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | let app = require('express')();
2 | let xmlparser = require('express-xml-bodyparser')({explicitArray: false});
3 | let mysql = require('mysql');
4 |
5 | let tableName = 'contact';
6 |
7 | function transform(sobject) {
8 | return {
9 | 'id': sobject['sf:id'],
10 | 'name': sobject['sf:firstname'] + ' ' + sobject['sf:lastname'],
11 | 'email': sobject['sf:email']
12 | };
13 | }
14 |
15 | let pool = mysql.createPool(process.env.CLEARDB_DATABASE_URL || 'mysql://root@localhost/demo');
16 |
17 | // create the table if it doesn't exist
18 | pool.query(`SELECT * FROM ${tableName}`, function(err) {
19 | if ((err != null) && (err.code == 'ER_NO_SUCH_TABLE')) {
20 | pool.query('create table contact (id VARCHAR(18) PRIMARY KEY, name VARCHAR(128), email VARCHAR(128))');
21 | }
22 | });
23 |
24 | function ack() {
25 | return `
26 |
27 |
28 |
29 | true
30 |
31 |
32 | `;
33 | }
34 |
35 | function nack(errorMessage) {
36 | return `
37 |
38 |
39 |
40 | soap:Receiver
41 | ${errorMessage}
42 |
43 |
44 | `;
45 | }
46 |
47 | app.use(xmlparser);
48 |
49 | app.post('/', function(req, res) {
50 |
51 | try {
52 | let sobject = req.body['soapenv:envelope']['soapenv:body']['notifications']['notification']['sobject'];
53 | let data = transform(sobject);
54 |
55 | pool.query(`INSERT INTO ${tableName} SET ? ON DUPLICATE KEY UPDATE ?`, [data, data], function(err) {
56 | if (err != null) {
57 | console.error('Database error', err.message);
58 | res.status(500).send(nack(err.message));
59 | }
60 | else {
61 | res.status(200).send(ack());
62 | }
63 | });
64 | }
65 | catch (err) {
66 | console.error('Uncaught error', err);
67 | res.status(500).send(nack(err.message));
68 | }
69 | });
70 |
71 | app.listen(process.env.PORT || 5000);
72 |
--------------------------------------------------------------------------------