Last active
February 7, 2022 14:45
-
-
Save federicofazzeri/87fca43929e31a9df24245331735d602 to your computer and use it in GitHub Desktop.
Node Express-based REST API (CRUD) using Firebase cloud Functions and FireStore cloud database + Babel config (The current Node version running in Cloud Functions is 6.10)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"presets": [ | |
["env", { | |
"targets": { | |
"node": "6.10" | |
} | |
}] | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const functions = require('firebase-functions'); | |
const express = require('express') | |
const compression = require('compression') | |
const bodyParser = require('body-parser'); | |
const cors = require('cors'); | |
const jwt = require('jsonwebtoken'); | |
const {jwtSecret} = require('./config'); | |
const {ctrlLogin, ctrlRead, ctrlCreate, ctrlUpdate, ctrlDelete} = require('./controller'); | |
const app = express(); | |
app.use(bodyParser.json()); | |
app.use(compression()) | |
app.use(cors()); | |
app.use(function(req, res, next) { | |
const token = req.query.t; | |
if(req.path == '/login') { | |
next(); | |
} else if ( ! token ) { | |
res.status(403).send({ | |
success: false, | |
message: 'No token provided.' | |
}); | |
} else { | |
jwt.verify(token, jwtSecret, function(err, decoded) { | |
if (err) { | |
return res.json({ success: false, message: 'Failed to authenticate token.' }); | |
} else { | |
req.decoded = decoded; | |
next(); | |
} | |
}); | |
} | |
}); | |
app.post('/login', | |
ctrlLogin, | |
(req, res) => res.json(res.data) | |
); | |
app.get('/api/:doc', | |
ctrlRead, | |
(req, res) => res.json(res.data) | |
); | |
app.get('/api/:doc', | |
ctrlRead, | |
(req, res) => res.json(res.data) | |
); | |
app.post('/api/:doc', | |
ctrlCreate, | |
(req, res) => res.json(res.data) | |
); | |
app.put('/api/:doc/:id', | |
ctrlUpdate, | |
(req, res) => res.json(res.data) | |
); | |
app.delete('/api/:doc:/id', | |
ctrlDelete, | |
(req, res) => res.json(res.data) | |
); | |
app.use( (err, req, res, next) => { | |
console.log(err) | |
res.status(500).send('An error occurred') | |
}); | |
exports.app = functions.https.onRequest(app); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
exports.jwtSecret = ''; | |
exports.collectionName = ''; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const jwt = require('jsonwebtoken'); | |
const {checkLogin, getDocNames, getDoc, createDoc, updateDoc, deleteDoc} = require('./model'); | |
const {jwtSecret} = require('./config'); | |
exports.ctrlLogin = (req, res, next) => { | |
login(req) | |
.then( logged => { | |
if( ! logged ) { | |
res.data = { | |
success: false, | |
message: 'Authentication failed' | |
}; | |
} else { | |
res.data = { | |
success: true, | |
token: jwt.sign({logged: true}, jwtSecret) | |
}; | |
} | |
next(); | |
}) | |
.catch( err => { | |
next(new Error(err)); | |
}); | |
}; | |
exports.ctrlRead = (req, res, next) => { | |
getData(req) | |
.then( data => { | |
res.data = data; | |
next(); | |
}) | |
.catch( err => { | |
next(new Error(err)); | |
}); | |
}; | |
exports.ctrlCreate = (req, res, next) => { | |
addData(req) | |
.then( data => { | |
res.data = data; | |
next(); | |
}) | |
.catch( err => { | |
next(new Error(err)); | |
}); | |
}; | |
exports.ctrlUpdate = (req, res, next) => { | |
updateData(req) | |
.then( data => { | |
res.data = data; | |
next(); | |
}) | |
.catch( err => { | |
next(new Error(err)); | |
}); | |
}; | |
exports.ctrlDelete = (req, res, next) => { | |
deleteData(req) | |
.then( data => { | |
res.data = data; | |
next(); | |
}) | |
.catch( err => { | |
next(new Error(err)); | |
}); | |
}; | |
async function deleteData(req) { | |
const id = req.params.id; | |
var doc = await getData(req); | |
return await deleteDoc(doc, id); | |
} | |
async function updateData(req) { | |
const updatedItem = req.body; | |
const id = req.params.id; | |
var doc = await getData(req); | |
return await updateDoc(doc, updatedItem, id); | |
} | |
async function addData(req) { | |
const newItem = req.body; | |
var doc = await getData(req); | |
return await createDoc(doc, newItem); | |
} | |
async function getData(req) { | |
try { | |
let docName = req.params.doc; | |
await isValidDocName(docName); | |
return await getDoc(docName); | |
} catch(err) { | |
throw err; | |
} | |
} | |
async function login(req) { | |
try { | |
let pwd = req.body.pwd; | |
return await checkLogin(pwd); | |
} catch(err) { | |
throw err; | |
} | |
} | |
async function isValidDocName(docName) { | |
try { | |
const docNames = await getDocNames(); | |
if( ! docNames.includes(docName) ) { | |
throw `${docName} is not a valid document name`; | |
} | |
return docName; | |
} catch(err) { | |
throw err; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const admin = require('firebase-admin'); | |
const functions = require('firebase-functions'); | |
admin.initializeApp(functions.config().firebase); | |
const db = admin.firestore(); | |
const {collectionName} = require('./config'); | |
const addDoc = { | |
tasks: (doc, newItem) => { | |
newItem.id = ++doc.data.index; | |
doc.data.list.push(newItem); | |
return setFirebaseDoc(doc); | |
}, | |
days: (doc, newItem) => { | |
doc.data = Object.assign({}, doc.data, newItem) | |
return setFirebaseDoc(doc); | |
}, | |
users: (doc, newItem) => { | |
if( ! doc.data.list ) { | |
doc.data.list = []; | |
} | |
doc.data.list.push(newItem); | |
return setFirebaseDoc(doc); | |
} | |
} | |
const updateDoc = { | |
tasks: (doc, updatedItem, id) => { | |
delete updatedItem.id; | |
doc.data.list = doc.data.list | |
.map( task => ( task.id == id ) ? Object.assign({}, task, updatedItem) : task ) | |
return setFirebaseDoc(doc); | |
}, | |
days: (doc, updatedItem, id) => { | |
doc.data[id] = updatedItem; | |
return setFirebaseDoc(doc); | |
} | |
} | |
const deleteDoc = { | |
tasks: (doc, id) => { | |
doc.data.list = doc.data.list | |
.filter( task => task.id != id ) | |
return setFirebaseDoc(doc); | |
}, | |
days: (doc, id) => { | |
delete doc.data[id]; | |
return setFirebaseDoc(doc); | |
} | |
} | |
exports.checkLogin = pwd => | |
getFirebaseDoc('users') | |
.then( data => { | |
return data.data.list.some( doc => doc.pwd == pwd ) | |
}); | |
exports.getDocNames = () => getAllFirebaseDocsIds(); | |
exports.getDoc = docName => getFirebaseDoc(docName) | |
exports.createDoc = (doc, newItem) => | |
addDoc[doc.id](doc, newItem) | |
exports.updateDoc = (doc, updatedItem, id) => | |
updateDoc[doc.id](doc, updatedItem, id) | |
exports.deleteDoc = (doc, id) => | |
deleteDoc[doc.id](doc, id) | |
async function getAllFirebaseDocsIds() { | |
let ids = []; | |
let snapshot = await db.collection(collectionName).get(); | |
snapshot.forEach( doc => ids.push(doc.id) ); | |
return ids; | |
} | |
async function getFirebaseDoc(docName) { | |
try { | |
let res = { | |
id: docName | |
}; | |
let snapshot = await db.collection(collectionName).get(); | |
snapshot.forEach( doc => { | |
if(doc.id == docName) { | |
res.data = doc.data(); | |
} | |
}); | |
return res; | |
} catch(err) { | |
throw err; | |
} | |
}; | |
async function setFirebaseDoc(updatedDoc) { | |
try { | |
let docRef = db.collection(collectionName).doc(updatedDoc.id); | |
await docRef.set(updatedDoc.data); | |
return updatedDoc; | |
} catch(err) { | |
throw err; | |
} | |
} | |
Thank you so much for these great notes, they really helped especially how your express is handling url calls, and separation of functions into other files, however i do have one question, please share an example of how the url are going to be called or consumed by other platforms.
Cool
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
HI, I have a question, what i should to put here "exports.jwtSecret = '';"