Firebase-Express backend functions - written as in Node.js May be published to firebase or use local firebase server utility for testing.
|
create project on firebase console
|
|
|
register your new app: <project-name>
|
Take note of the SDK/config data
|
|
npm install -g firebase-tools
|
Global install of CLI
|
|
firebase login
|
Login to Firebase (may launch browser window)
|
|
mkdir <project-name>
|
Create your project directory
|
|
cd <project-name>
|
Move into your project directory
|
|
firebase init
|
Choose "functions"
|
|
JavaScript or Typescript
|
Choose your coding language preference. (JavaScript)
|
|
eslint ?
|
Do you wish to use eslint? Suggest - 'No"
|
|
code .
|
run VS Code
|
|
Add two folders under the "functions" folder
|
util, handlers
|
|
Copy the code snippets to the util folder each in its own .js file.
|
admin, config, validators, and fbAuth
|
|
Copy the users code to user.js file in the handlers folder
|
users
|
|
Replace the contents of index.js with the code listed here.
|
index.js
|
|
-
|
-
|
|
firebase serve
|
CLI command to test code on your local host.
|
|
firebase deploy
|
|
exports.helloworld = functions.https.onRequest( (request, response) => { response.send('Hello world!') });https://<...firebase assigned server...>.<...project name...>-<...some random letters ...>.cloudfunctions.net/helloWorld// Import admin
const admn = require('firebase-admin')
// Initialize app
admin.initializeApp
// Setup a 'shortcut' variable to save keystrokes
const db = admin.firestore()
// Export
module.exports = { admin, db }
const config = {
apiKey: "*<...your key goes here...>*",
authDomain: "*<...project id...>*.firebaseapp.com",
databaseURL: "https://*<...project id...>*-default-rtdb.firebaseio.com",
projectId: "*<...project id...>*",
storageBucket: "*<...project id...>*.appspot.com",
messagingSenderId: "*< ...messaging sender id...>*",
appId: "*<...app id...>*"
};
module.exports = { config }
const { admin, db } = require('./admin')
module.exports = (req, res, next) => {
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer ")) {
idToken = req.headers.authorization.split("Bearer ")[1];
} else {
console.error("No token found");
return res.status(403).json({error: "Unauthorized"});
}
admin.auth().verifyIdToken(idToken)
.then((decodedToken) => {
req.user = decodedToken;
return db.collection("users")
.where("userId", "==", req.user.uid)
.limit(1)
.get();
})
.then((data) => {
req.user.userHandle = data.docs[0].data().userHandle;
return next();
})
.catch((err) =>{
console.error("Error while verifying token", err);
return res.status(403).json(err);
});
};
// -------------------- Route without authentication -------------------- //
app.post('/signup', signup);
// -------------------- Route with authentication -------------------- //
// (e.g. requires a valid token)
app.post('/user', FBAuth, addUserDetails);
// --------------- HELPER FUNCTIONS ---------------
// ----- Email validation using a regex expression -----
const isEmail = (email) => {
const regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (email.match(regEx)) return true
else return false
}
// ----- Non-blank/Required entry -----
const isEmpty = (string) => {
if (string.trim() === '') return true;
else return false;
};
// --------------- SIGNUP and LOGIN Functions ---------------
// ---------- Validate "SignUp" fields: userid, password, confirm password, & email
exports.validateSignupData = (data) => {
const errors = {}
// validate email
if (isEmpty(data.email)) {
errors.email = "Must not be empty"
} else if (!isEmail(data.email)) {
errors.email = "Must be a valid email address"
}
// validate password
if (isEmpty(data.password)) errors.password = "Must not be empty";
// validate confirmpassword
if (data.confirmPassword !== data.password) errors.confirmPassword = "Passwords must match";
// validate userHandle
if (isEmpty(data.userHandle)) errors.userHandle = "Must not be empty";
return {
errors,
valid: Object.keys(errors).length === 0 ? true : false
}
}
// ---------- Validate "Login" fields: email & password
exports.validateLoginData = (data) => {
const errors = {}
// validate email
if (isEmpty(data.email)) errors.email = "Must not be empty"
// validate password
if (isEmpty(data.password)) errors.password = "Must not be empty";
return {
errors,
valid: Object.keys(errors).length === 0 ? true : false
}
}
exports.reduceUserDetails = (data) => {
let userDetails = {}
// strip out empty string fields so that they are not added
// to the document
if(!isEmpty(data.firstName.trim())) userDetails.firstName = data.firstName
if(!isEmpty(data.lastName.trim())) userDetails.lastName = data.lastName
if(userDetails.length > 0 ){
userDetails.updatedAt = new Date().toISOString()
}
return userDetails
}
// Import Admin
const { admin, db } = require ('../util/admin')
const { config } = require("../util/config")
const { v4: uuidv4 } = require('uuid')
const firebase = require('firebase')
firebase.initializeApp(config)
const { validateSignupData,
validateLoginData,
reduceUserDetails
} = require('../util/validators')
// --------------------- SIGNUP ------------------- //
exports.signup = (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassword: req.body.confirmPassword,
userHandle: req.body.userHandle,
firstName: req.body.firstName,
lastName: req.body.lastName,
};
const { valid, errors } = validateSignupData(newUser)
if(!valid) return res.status(400).json(errors)
const noImg = 'no-img.png'
let token;
let userId;
db.doc(`/users/${newUser.userHandle}`).get()
.then((doc) => {
if (doc.exists) {
return res.status(400).json({userHandle: "This handle is already taken."});
} else {
return firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password);
}
})
.then((data) => {
userId = data.user.uid;
return data.user.getIdToken();
})
.then((idToken) => {
token = idToken;
const userCredentials = {
// userHandle: newUser.userHandle,
email: newUser.email,
createdAT: new Date().toISOString(),
updatedAt: new Date().toISOString(),
userId,
firstName: newUser.firstName,
lastName: newUser.lastName,
imageUrl: `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${noImg}?alt=media`
};
db.doc(`/users/${newUser.userHandle}`).set(userCredentials);
})
.then(() => {
return res.status(201).json({token});
}
)
.catch((err) => {
console.error(err);
if (err.code === "auth/email-already-in-use") {
return res.status(400).json({email: "Email is already in use."});
} else {
return res.status(500).json({error: err.code});
}
});
}
// --------------------- LOGIN ------------------- //
exports.login = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password,
};
const { valid, errors } = validateLoginData(user)
if(!valid) return res.status(400).json(errors)
firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
// TODO: Rtv userId from user's file (i.e. userHandle)
return data.user.getIdToken();
})
.then((token) => {
return res.json({token});
})
.catch((err) => {
console.error(err);
if (err.code === "auth/wrong-password") {
return res.status(403).json({general: "Wrong credentials, please try again"});
} else return res.status(500).json({error: err.code});
});
}
// --------------------- GET ALL USERS ------------------- //
exports.getAllUsers = (req, res) => {
db
.collection("users")
.orderBy("userId", "asc")
.get()
.then((data) => {
const users = []
console.log(data.doc)
data.forEach((doc) => {
users.push({
userId: doc.id,
userHandle: doc.data().userHandle,
firstName: doc.data().firstName,
lastName: doc.data().lastName,
email: doc.data().email,
createdAt: doc.data().createdAt,
updatedAt: doc.data().updatedAt,
});
});
return res.json(users);
})
.catch((err) => console.error(err));
}
// --------------------- ADD USER DETAILS ------------------- //
exports.addUserDetails = (req, res) => {
let userDetails = reduceUserDetails(req.body)
// TODO: Replace userHandle with userId once rtvd
db.doc(`/user/${req.user.userHandle}`)
.update(userDetails)
.then(() => {
return res.json({ message: 'Details added successfully'})
})
.catch(err => {
console.error(err)
return res.status(500).json({ error: err.code})
})
}
// --------------------- UPLOAD AN IMAGE ------------------- //
exports.uploadImage = (req, res) => {
const BusBoy = require("busboy");
const path = require("path");
const os = require("os");
const fs = require("fs");
const busboy = new BusBoy({ headers: req.headers });
let imageToBeUploaded = {};
let imageFileName;
// String for image token
let generatedToken = uuidv4();
busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
console.log(fieldname, file, filename, encoding, mimetype);
if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
return res.status(400).json({ error: "Wrong file type submitted" });
}
// my.image.png => ['my', 'image', 'png']
const imageExtension = filename.split(".")[filename.split(".").length - 1];
// 32756238461724837.png
imageFileName = `${Math.round(
Math.random() * 1000000000000
).toString()}.${imageExtension}`;
const filepath = path.join(os.tmpdir(), imageFileName);
imageToBeUploaded = { filepath, mimetype };
file.pipe(fs.createWriteStream(filepath));
});
busboy.on("finish", () => {
admin
.storage()
.bucket()
.upload(imageToBeUploaded.filepath, {
resumable: false,
metadata: {
metadata: {
contentType: imageToBeUploaded.mimetype,
//Generate token to be appended to imageUrl
firebaseStorageDownloadTokens: generatedToken,
},
},
})
.then(() => {
// Append token to url
const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media&token=${generatedToken}`;
// console.log('***** User info ******: ', req.user)
// TODO: Replace userHandle with userId/uid
// Then remove userHandle from database documents
return db.doc(`/users/${req.user.userHandle}`).update({ imageUrl });
})
.then(() => {
return res.json({ message: "image uploaded successfully" });
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: "something went wrong" });
});
});
busboy.end(req.rawBody);
};
// Imports
const functions = require("firebase-functions");
const app = require('express')();
const FBAuth = require('./util/fbAuth')
// Import Handlers
const { signup,
login,
getAllUsers,
uploadImage,
addUserDetails
} = require('./handlers/users')
// ** ----- Routes ----- //
// -------------------- USERS -------------------- //
app.post('/signup', signup);
app.post('/login', login);
app.post('/user/image', FBAuth, uploadImage)
app.post('/user', FBAuth, addUserDetails)
app.get('/users', FBAuth, getAllUsers);
// --------------------- EXPORT ALL FUNCS -------------------- //
exports.api = functions.https.onRequest(app)
;
No comments yet.
Cheatography n. The art of mapping complex subjects into clear, instantly navigable reference guides.