├── .gitignore ├── README.md ├── importFirebaseToAlgolia.js ├── loadFirebase.js ├── package.json ├── syncFirebaseToAlgolia.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | serviceAccountKey.json 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # algolia-firebase-nodejs example 2 | 3 | Algolia is a nice way to make your Firebase data searchable. Out of the box you get prefix matching, typo tolerance and more advanced features like faceting and filtering. This repository contains contains example code and instructions on how to run it. 4 | 5 | ## Key files 6 | 7 | 1. `importFirebaseToAlgolia.js`: Shows how to do a one-time import of all Firebase data under a specific ref. 8 | 1. `syncFirebaseToAlgolia.js`: Shows how to listen to changes of children of a ref and sync them to Algolia. 9 | 1. `loadFirebase.js`: Loads test data into Firebase so it can be sent to Algolia later. 10 | 11 | ## Prerequisites 12 | 13 | #### Firebase 14 | 15 | Create a [new Realtime Database](https://console.firebase.google.com), or you can use one that already exists. We'll be using the ref "contacts" for the whole example, so make sure there isn't any data there already. 16 | 17 | #### Algolia 18 | 19 | Create a [new Algolia application](https://www.algolia.com/manage/applications), or use one that already exists. We'll be creating an index called "contacts", so make sure that doesn't already exist. 20 | 21 | ## Usage 22 | 23 | Clone this repository. 24 | 25 | ``` 26 | git clone git@github.com:algolia/algolia-firebase-nodejs.git 27 | ``` 28 | 29 | Install dependencies with `npm install` or `yarn`. 30 | 31 | ``` 32 | yarn 33 | ``` 34 | 35 | Create a file called `.env`. Substitute your values for the placeholders: 36 | 37 | ``` shell 38 | ALGOLIA_APP_ID= 39 | ALGOLIA_API_KEY= 40 | FIREBASE_DATABASE_URL=https://.firebaseio.com 41 | ``` 42 | 43 | Make sure the Algolia API key you've chosen has write access. If in doubt, use your Admin API Key. 44 | 45 | Download a service account JSON file from Firebase. This will be used to authenticate you as an admin so you can read and write data. From the Firebase console for your database, click the gear icon and choose "Project Settings". Go to the "Service Accounts" tab. Click "Generate New Private Key". Move the downloaded file into this directory and name it `serviceAccountKey.json`. 46 | 47 | This file and .env are in the .gitgnore, so you don't have to worry about accidentally checking them in. 48 | 49 | #### Load example data 50 | 51 | Load example contacts data into your Firebase database running: 52 | 53 | ``` 54 | node loadFirebase 55 | ``` 56 | 57 | Look at the code in the `loadFirebase.js` file to see what is happening. If this is successful, you will see a message "Contacts loaded to firebase" and you will be able to see data in your Firebase database in the console. 58 | 59 | #### First-time import into Algolia 60 | 61 | Import these contacts records into Algolia by running: 62 | 63 | ``` 64 | node importFirebaseToAlgolia 65 | ``` 66 | 67 | If this is successful, you should see "Firebase<>Algolia import done". Your contact records have been imported into Algolia and you can see them in your dashboard. 68 | 69 | #### Ongoing sync to Algolia 70 | 71 | In a real application, you will want to listen for Firebase changes and index them as they come in. To do this, run: 72 | 73 | ``` 74 | node syncFirebaseToAlgolia 75 | ``` 76 | 77 | This process will keep running indefinitely, listening for changes to the "contacts" node in your Firebase database. When it receives a change, it will add, update or delete the record in Algolia and then log to the console. 78 | -------------------------------------------------------------------------------- /importFirebaseToAlgolia.js: -------------------------------------------------------------------------------- 1 | var dotenv = require('dotenv'); 2 | var firebaseAdmin = require("firebase-admin"); 3 | var algoliasearch = require('algoliasearch'); 4 | 5 | // load values from the .env file in this directory into process.env 6 | dotenv.load(); 7 | 8 | var serviceAccount = require("./serviceAccountKey.json"); 9 | firebaseAdmin.initializeApp({ 10 | credential: firebaseAdmin.credential.cert(serviceAccount), 11 | databaseURL: process.env.FIREBASE_DATABASE_URL 12 | }); 13 | var database = firebaseAdmin.database(); 14 | 15 | var algolia = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY); 16 | var index = algolia.initIndex('contacts'); 17 | 18 | var contactsRef = database.ref("/contacts"); 19 | contactsRef.once('value', initialImport); 20 | function initialImport(dataSnapshot) { 21 | // Array of data to index 22 | var objectsToIndex = []; 23 | // Get all objects 24 | var values = dataSnapshot.val(); 25 | // Process each child Firebase object 26 | dataSnapshot.forEach((function(childSnapshot) { 27 | // get the key and data from the snapshot 28 | var childKey = childSnapshot.key; 29 | var childData = childSnapshot.val(); 30 | // Specify Algolia's objectID using the Firebase object key 31 | childData.objectID = childKey; 32 | // Add object for indexing 33 | objectsToIndex.push(childData); 34 | })) 35 | // Add or update new objects 36 | index.saveObjects(objectsToIndex, function(err, content) { 37 | if (err) { 38 | throw err; 39 | } 40 | console.log('Firebase<>Algolia import done'); 41 | process.exit(0); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /loadFirebase.js: -------------------------------------------------------------------------------- 1 | var dotenv = require('dotenv'); 2 | var firebaseAdmin = require("firebase-admin"); 3 | var algoliasearch = require('algoliasearch'); 4 | 5 | // load values from the .env file in this directory into process.env 6 | dotenv.load(); 7 | 8 | var serviceAccount = require("./serviceAccountKey.json"); 9 | firebaseAdmin.initializeApp({ 10 | credential: firebaseAdmin.credential.cert(serviceAccount), 11 | databaseURL: process.env.FIREBASE_DATABASE_URL 12 | }); 13 | var database = firebaseAdmin.database(); 14 | 15 | // listening is all configured, let's add some contacts 16 | Promise.all([ 17 | database.ref('/contacts/josh').set({ 18 | name: 'Josh', 19 | city: 'San Francisco' 20 | }), 21 | database.ref('/contacts/tim').set({ 22 | name: 'Tim', 23 | city: 'Paris' 24 | })]).then(function() { 25 | console.log("Contacts loaded to firebase"); 26 | process.exit(0); 27 | }).catch((function(error) { 28 | console.error("Error loading firebase", error); 29 | process.exit(-1); 30 | })); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "algolia-firebase-nodejs", 4 | "version": "1.0.0", 5 | "description": "Shows how to index Firebase data with Algolia", 6 | "main": "index.js", 7 | "scripts": { 8 | "load": "node loadFirebase", 9 | "import": "node importFirebaseToAlgolia", 10 | "sync": "node syncFirebaseToAlgolia" 11 | }, 12 | "author": { 13 | "name": "Algolia, Inc.", 14 | "url": "https://www.algolia.com" 15 | }, 16 | "license": "ISC", 17 | "dependencies": { 18 | "algoliasearch": "^3.22.1", 19 | "dotenv": "^4.0.0", 20 | "firebase-admin": "^4.2.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /syncFirebaseToAlgolia.js: -------------------------------------------------------------------------------- 1 | var dotenv = require('dotenv'); 2 | var firebaseAdmin = require("firebase-admin"); 3 | var algoliasearch = require('algoliasearch'); 4 | 5 | // load values from the .env file in this directory into process.env 6 | dotenv.load(); 7 | 8 | var serviceAccount = require("./serviceAccountKey.json"); 9 | firebaseAdmin.initializeApp({ 10 | credential: firebaseAdmin.credential.cert(serviceAccount), 11 | databaseURL: process.env.FIREBASE_DATABASE_URL 12 | }); 13 | var database = firebaseAdmin.database(); 14 | 15 | var algolia = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY); 16 | var index = algolia.initIndex('contacts'); 17 | 18 | var contactsRef = database.ref("/contacts"); 19 | 20 | contactsRef.on('child_added', addOrUpdateIndexRecord); 21 | contactsRef.on('child_changed', addOrUpdateIndexRecord); 22 | contactsRef.on('child_removed', deleteIndexRecord); 23 | 24 | function addOrUpdateIndexRecord(dataSnapshot) { 25 | // Get Firebase object 26 | var firebaseObject = dataSnapshot.val(); 27 | // Specify Algolia's objectID using the Firebase object key 28 | firebaseObject.objectID = dataSnapshot.key; 29 | // Add or update object 30 | index.saveObject(firebaseObject, function(err, content) { 31 | if (err) { 32 | throw err; 33 | } 34 | console.log('Firebase<>Algolia object saved', firebaseObject.objectID); 35 | }); 36 | } 37 | 38 | function deleteIndexRecord(dataSnapshot) { 39 | // Get Algolia's objectID from the Firebase object key 40 | var objectID = dataSnapshot.key; 41 | // Remove the object from Algolia 42 | index.deleteObject(objectID, function(err, content) { 43 | if (err) { 44 | throw err; 45 | } 46 | console.log('Firebase<>Algolia object deleted', objectID); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/jsonwebtoken@^7.1.33": 6 | version "7.2.1" 7 | resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.1.tgz#44a0282b48d242d0760eab0ce6cf570a62eabf96" 8 | dependencies: 9 | "@types/node" "*" 10 | 11 | "@types/node@*": 12 | version "8.0.7" 13 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.7.tgz#fb0ad04b5b6f6eabe0372a32a8f1fbba5c130cae" 14 | 15 | agentkeepalive@^2.2.0: 16 | version "2.2.0" 17 | resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-2.2.0.tgz#c5d1bd4b129008f1163f236f86e5faea2026e2ef" 18 | 19 | algoliasearch@^3.22.1: 20 | version "3.24.0" 21 | resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.24.0.tgz#d0a6ac2963b781d2fb059a3a853fe18765673346" 22 | dependencies: 23 | agentkeepalive "^2.2.0" 24 | debug "^2.6.8" 25 | envify "^4.0.0" 26 | es6-promise "^4.1.0" 27 | events "^1.1.0" 28 | foreach "^2.0.5" 29 | global "^4.3.2" 30 | inherits "^2.0.1" 31 | isarray "^2.0.1" 32 | load-script "^1.0.0" 33 | object-keys "^1.0.11" 34 | querystring-es3 "^0.2.1" 35 | reduce "^1.0.1" 36 | semver "^5.1.0" 37 | tunnel-agent "^0.6.0" 38 | 39 | base64url@2.0.0, base64url@^2.0.0: 40 | version "2.0.0" 41 | resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" 42 | 43 | buffer-equal-constant-time@1.0.1: 44 | version "1.0.1" 45 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 46 | 47 | debug@^2.6.8: 48 | version "2.6.8" 49 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" 50 | dependencies: 51 | ms "2.0.0" 52 | 53 | dom-walk@^0.1.0: 54 | version "0.1.1" 55 | resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" 56 | 57 | dotenv@^4.0.0: 58 | version "4.0.0" 59 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" 60 | 61 | ecdsa-sig-formatter@1.0.9: 62 | version "1.0.9" 63 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" 64 | dependencies: 65 | base64url "^2.0.0" 66 | safe-buffer "^5.0.1" 67 | 68 | envify@^4.0.0: 69 | version "4.0.0" 70 | resolved "https://registry.yarnpkg.com/envify/-/envify-4.0.0.tgz#f791343e3d11cc29cce41150300a8af61c66cab0" 71 | dependencies: 72 | esprima "~3.1.0" 73 | through "~2.3.4" 74 | 75 | es6-promise@^4.1.0: 76 | version "4.1.1" 77 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" 78 | 79 | esprima@~3.1.0: 80 | version "3.1.3" 81 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" 82 | 83 | events@^1.1.0: 84 | version "1.1.1" 85 | resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" 86 | 87 | faye-websocket@0.9.3: 88 | version "0.9.3" 89 | resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.9.3.tgz#482a505b0df0ae626b969866d3bd740cdb962e83" 90 | dependencies: 91 | websocket-driver ">=0.5.1" 92 | 93 | firebase-admin@^4.2.1: 94 | version "4.2.1" 95 | resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-4.2.1.tgz#91794bfc214ee21decc765d308411166a05c0b20" 96 | dependencies: 97 | "@types/jsonwebtoken" "^7.1.33" 98 | faye-websocket "0.9.3" 99 | jsonwebtoken "7.1.9" 100 | 101 | foreach@^2.0.5: 102 | version "2.0.5" 103 | resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" 104 | 105 | global@^4.3.2: 106 | version "4.3.2" 107 | resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" 108 | dependencies: 109 | min-document "^2.19.0" 110 | process "~0.5.1" 111 | 112 | hoek@2.x.x: 113 | version "2.16.3" 114 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 115 | 116 | inherits@^2.0.1: 117 | version "2.0.3" 118 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 119 | 120 | isarray@^2.0.1: 121 | version "2.0.1" 122 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" 123 | 124 | isemail@1.x.x: 125 | version "1.2.0" 126 | resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" 127 | 128 | joi@^6.10.1: 129 | version "6.10.1" 130 | resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06" 131 | dependencies: 132 | hoek "2.x.x" 133 | isemail "1.x.x" 134 | moment "2.x.x" 135 | topo "1.x.x" 136 | 137 | jsonwebtoken@7.1.9: 138 | version "7.1.9" 139 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.1.9.tgz#847804e5258bec5a9499a8dc4a5e7a3bae08d58a" 140 | dependencies: 141 | joi "^6.10.1" 142 | jws "^3.1.3" 143 | lodash.once "^4.0.0" 144 | ms "^0.7.1" 145 | xtend "^4.0.1" 146 | 147 | jwa@^1.1.4: 148 | version "1.1.5" 149 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" 150 | dependencies: 151 | base64url "2.0.0" 152 | buffer-equal-constant-time "1.0.1" 153 | ecdsa-sig-formatter "1.0.9" 154 | safe-buffer "^5.0.1" 155 | 156 | jws@^3.1.3: 157 | version "3.1.4" 158 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" 159 | dependencies: 160 | base64url "^2.0.0" 161 | jwa "^1.1.4" 162 | safe-buffer "^5.0.1" 163 | 164 | load-script@^1.0.0: 165 | version "1.0.0" 166 | resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" 167 | 168 | lodash.once@^4.0.0: 169 | version "4.1.1" 170 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 171 | 172 | min-document@^2.19.0: 173 | version "2.19.0" 174 | resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" 175 | dependencies: 176 | dom-walk "^0.1.0" 177 | 178 | moment@2.x.x: 179 | version "2.18.1" 180 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" 181 | 182 | ms@2.0.0: 183 | version "2.0.0" 184 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 185 | 186 | ms@^0.7.1: 187 | version "0.7.3" 188 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff" 189 | 190 | object-keys@^1.0.11, object-keys@~1.0.0: 191 | version "1.0.11" 192 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" 193 | 194 | process@~0.5.1: 195 | version "0.5.2" 196 | resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" 197 | 198 | querystring-es3@^0.2.1: 199 | version "0.2.1" 200 | resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" 201 | 202 | reduce@^1.0.1: 203 | version "1.0.1" 204 | resolved "https://registry.yarnpkg.com/reduce/-/reduce-1.0.1.tgz#14fa2e5ff1fc560703a020cbb5fbaab691565804" 205 | dependencies: 206 | object-keys "~1.0.0" 207 | 208 | safe-buffer@^5.0.1: 209 | version "5.1.1" 210 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 211 | 212 | semver@^5.1.0: 213 | version "5.3.0" 214 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 215 | 216 | through@~2.3.4: 217 | version "2.3.8" 218 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 219 | 220 | topo@1.x.x: 221 | version "1.1.0" 222 | resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5" 223 | dependencies: 224 | hoek "2.x.x" 225 | 226 | tunnel-agent@^0.6.0: 227 | version "0.6.0" 228 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 229 | dependencies: 230 | safe-buffer "^5.0.1" 231 | 232 | websocket-driver@>=0.5.1: 233 | version "0.6.5" 234 | resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" 235 | dependencies: 236 | websocket-extensions ">=0.1.1" 237 | 238 | websocket-extensions@>=0.1.1: 239 | version "0.1.1" 240 | resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7" 241 | 242 | xtend@^4.0.1: 243 | version "4.0.1" 244 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 245 | --------------------------------------------------------------------------------