The majority of the work today has been continuations of work from yesterday, just implementing business logic on the routes of my application. So, I figured I’d take a break from writing about that to talk about three functions that I’ve created to do very specific things in my application.
What's a utils
folder?
This is the directory where you put all of your little functions that have a hard time finding a home, but your application couldn’t live without them.
A utils
folder is the dumping ground for the unsung heros of many applications.
When you have to perform a transformation of your data that’s going to take more than a few lines of code, that you’ll have to reuse, it’s a good idea to put it into it’s own file that you can export to the rest of your application.
Why don’t we just copy paste? Well, this would violate two programming principles, DRY and separation of concerns.
Don't Repeat Yourself
Not only is repeating yourself monotonous, it’s also a bear to change if you’ve done it enough throughout your application. Imagine an algorithm that calculates the percent chance of rain today.
I don’t know how people do that, so I can’t show you an example. But, if you copy this all throughout your code in the different places that need to have access to this calculation, you’re going to be very upset when the Very Smart Scientific Weather Committee comes back with a new, more-accurate algorithm.
Take re-used parts of your code, and find ways to package them to be used in multiple places, while still being updated in one place.
All of the functions in my utils
folder are used in many places throughout my application!
Separation of Concerns
As programmers, we also don’t want to create functions that do a LOT of different things. We would rather have a LOT of functions that all do one thing. Why? Well, it makes these functions, more reusable!
What does this have to do with a utils
folder? Well, the functions I'm about to go over don't really have a place within the functions like getRoastsById
because that's not what they do! When we need to do something else, we should make a function for it. But, when we don't have a logical place for that function, becuase it's a “helper", we stick it in our utils
directory!
My utils
folder
I have three custom utils
so far:
insertStatement
updateStatement
objectKeysToCamel
Hopefully, by their names it is clear what they do, but let me share briefly the problem that they solve and how they work.
insertStatement
Problem: In many of the different services of my application, I will be required to perform an INSERT
query to my database. These statements require you to explicitly list out 1) the names of the columns and 2) the values. I shouldn't have to type these out in each route, so I created a function to do it for me.
Input: The function takes two parameters, table
a string that matches the name of a table in the database and obj
, a Javascript object that represents the model that the user wants to add to the database.
Output: An object with 1) a property formatted INSERT string with placeholder values and 2) an array of the values to be used in a parameterized query.
const { snakeCase } = require('change-case-commonjs');
function insertStatement(table, obj) {
const keys = Object.keys(obj);
const values = Object.values(obj);
let statement = `INSERT INTO ${table} (`;
// Add snake_case keys to the statement
const keyString = keys.map((key, i) => snakeCase(key)).join(', ');
statement += `${keyString}) VALUES (`;
// Add placeholders for the values
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
statement += `${placeholders}) RETURNING *;`;
// Return the query string and the values array
return {
text: statement,
values: values
};
}
module.exports = insertStatement;
updateStatement
Problem: Similar to the INSERT
statement, the UPDATE
statement requires you to explicity state both column names and values in your query. This syntax is different from an INSERT
statement. Through conditional logic, I could create a databaseQueryGenerator
function, but this also seems to violate separation of concerns. Would a function like that be deciding what kind of query you want, or generating syntax based on that?
Input: The function takes three parameters. obj
, a JavaScript object that represents the updated record. table
, a string that should match a table in the database. id
, an integer that matches the record to be updated with the new information.
Output: An object with 1) a property formatted UPDATE string with placeholder values and 2) an array of the values to be used in a parameterized query.
const { snakeCase } = require('change-case-commonjs');
function updateStatement(obj, table, id) {
const keys = Object.keys(obj);
const values = Object.values(obj);
let statement = `UPDATE ${table} SET `;
keys.forEach((key, index) => {
statement += `${snakeCase(key)} = $${index + 1}, `;
});
// Remove the last comma and space
statement = statement.slice(0, -2);
// Determine the correct ID column based on the table
const idColumn = table === 'users' ? 'username' : table === 'roasts' ? 'roast_id' : '';
// Finalize the statement with the WHERE clause
statement += ` WHERE ${idColumn} = $${keys.length + 1} RETURNING *;`;
return {
text: statement,
values: [...values, id]
};
}
module.exports = updateStatement
objectKeysToCamel
Problem: The style of my database is different from the style of my JavaScript. However, I'm not willing to compromise in either area. In my JS files, my naming convention uses camelCase, where in my database it uses snake_case. All property names of returned objects are the same, but formatted differently. To maintain this case standard, I would have to access properties in my JS using snake_case, but I don't like this.
Input: The function takes only one parameter, an obj
JavaScript object that should have its keys transformed into camelCase formatting.
Output: The same object, with camelCase formatted keys.
const { camelCase } = require('change-case-commonjs');
function objectKeysToCamel(obj) {
// Extract the keys and values
const keys = Object.keys(obj);
const values = Object.values(obj);
let camel = {}
// Change the formatting of each key, assigning it the proper value
keys.forEach((key, i) => {
const camelKey = camelCase(key);
camel[camelKey] = values[i]
})
// Return the new object
return camel;
}
module.exports = objectKeysToCamel;
Check Out the Project
If you want to keep up with the changes, fork and run locally, or even suggest code changes, here’s a link to the GitHub repo!
https://github.com/nmiller15/roast
The frontend application is currently deployed on Netlify! If you want to mess around with some features and see it in action, view it on a mobile device below.
https://knowyourhomeroast.netlify.app
Note: This deployment has no backend api, so accounts and roasts are not actually saved anywhere between sessions.
Top comments (0)