Skip to content

Instantly share code, notes, and snippets.

@federicofazzeri
Last active February 7, 2022 14:45
Show Gist options
  • Save federicofazzeri/87fca43929e31a9df24245331735d602 to your computer and use it in GitHub Desktop.
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)
{
"presets": [
["env", {
"targets": {
"node": "6.10"
}
}]
]
}
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);
exports.jwtSecret = '';
exports.collectionName = '';
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;
}
}
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;
}
}
@aloism
Copy link

aloism commented Jul 15, 2020

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.

@talesmgodois
Copy link

Cool

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment