Awhile back, a friend of mine suggested that to get deeper with my learning, I should attempt to build my own static site generator. I had this in the back of my head for the past couple of months and decided to take a break from my course work to dive into this project.
Read More...This morning I am going through chapter 18 of Eloquent Javascript. (almost done with this book!) The chapter focuses on HTTP requests, html forms, as well as javascript’s fetch function.
I’ve worked with fetch in a couple of my own projects, to make calls to me own API on routes being served up by Express. Let’s dive in.
Calling fetch
returns a promise that resolves to a response object containing information about the server’s response.
fetch("example/data.txt").then(response => {
console.log(response.status);
// → 200
console.log(response.headers.get("Content-Type"));
// → text/plain
});
For the last part of the day yesterday I began going through the testing part of the node course I am taking. I love the idea of testing and am eager to continue to learn how to use Jest in reference to node, express, and a working database.
Yesterday we walked through setting up a test environment, a test database, and refactoring our call to express for us to be able to use it in a testing suite. We installed Jest, as well as Supertest (helps testing with express routes without creating a server) from the NPM library.
A jest test looks like this:
const calculateTip = (total, tipPercent = .25) => total + (total * tipPercent);
// Jest gives us the global variable of test, so no need to require anything
test('Should calculate total with tip', () => {
const total = calculateTip(10,.3)
expect(total).toBe(13)
})
In my previous post, I wrote about the resources I have been and continue to use while learning Javascript. One of the best ones has been Eloquent Javascript.
The latest chapter was tough, but the more I sat with it and examined the code line by line, as well as inside of a debugger, the more I understood what was going on. I went through all of the code of the chapter and ran it in the FireFox Developer debugger OVER AND OVER again! Each time I had a realization, I commented near the line of code what was happening.
I feel like I have a pretty good handle of how the code now works and what exactly is happening behind the scenes. I believe what I am most impressed with about this chapter, and the book in general, is just how much can be done with PURE javascript.
Read More...Today I want to make two posts. The first of which (this one) will document what materials and courses I have been going through this year. I’ll list them out in the order that I went through them in case you’d like to do the same. It has been a steady progression of difficulty and I feel like I have learned a lot. It has not been without its ups and downs as well as pretty difficult moments, but these courses have helped solidify a lot of javascript for me.
Javascript Algorithms and Data Structures Certification (Eq. 300 Hours)
Today we will be learning how to send emails inside of nodeJS, using env variables (which I learned a couple weeks back), creating a production MongoDB database, as well as getting into testing inside of nodeJS. (this is something I am eager to get into, as there hasnt been any debugging yet)
We will be using SendGrid. This service is run by Twilio, which I got familiar with during my SMS Text/YNAB app I worked on a couple of weeks ago. The setup for sendgrid was extremely simple. Here is an example of it working.
const sgMail = require('@sendgrid/mail')
const sendGridAPIKey = "yourKeyHere"
sgMail.setApiKey(sendGridAPIKey)
sgMail.send({
to: 'jordan@jordanvidrine.com',
from: 'jordan@jordanvidrine.com',
subject: 'first sendgrid email',
text: 'From nodeJS'
})
Wow. What a week! Since last Tuesday up until this morning my time has been spent setting up, rehearsing, performing, and setting down from my Art Spark performance. It was the best musical performance and activity I have ever had the privilege to participate in! If you’d like to check out some of that info, feel free to browse through my artist facebook page
Now that I’m finished with the performance aspect of the grant, I will be able to devote more time to learning to code. So, lets move on!
The first part of my learning today was back to the uploading files section of the node course. We are doing this by using the multer npm package. I started off today with a challenge to:
Today I’ll be getting back to the NodeJS course I am working through on Udemy. I feel like I have learned a really good amount of great information from this course. I’ve been able to personally use every piece of knowledge from this course into various personal apps and experiments since I started the course.
The first thing we implemented today was the ability for the database to use timestamps. This was super simple and only involved adding an options object as an argument when creating the Schema for a model.
{
timestamps: true,
}
What this does is add a createdAt
and updatedAt
field to each item created in the database.
Since I was so interested in making my idea work, I spent this week diving into finishing my simple Budgeting/Text Messaging app instead of my regular developer course work I normally do. For today’s post I will give you an overview of the project and what I did to accomplish the goals I had when I set out.
Even with the YNAB app being well designed and easy to use, entering small and basic transactions can be done in an easier way in my opinion. I set out to save time by not having to log into the YNAB app, navigate to the appropriate budget account, and input the transaction. The two main things I wanted to be able to do were:
1. Get the amount left in a budget category by sms text
2. Enter transactions by sms text
After researching a bit I decided to use the Twilio service to serve as the SMS interaction between the express routes I would create to interact with the YNAB data. I went through the basic walkthrough on their site to get familiar with the service, but decided I would build the app and test it via Postman before incorporating the Twilio service.
One big reason for this is that Twilio gives you a free trial credit of $15.00 that slowly gets used up when sending and receiving messages. I knew I’d be sending ALOT of back and forth during the testing so I didnt want to dwindle this balance.
This for me is getting to the point where it is very straightforward and the syntax is finally starting to look normal and make sense.
The gist of what’s happening here is that I am telling express to use the bodyParser (needed for the Twilio Integration), express.json()
method, and lastly (at the bottom of the code, but before the app.listen()
call) to use my custom error handler. I also tell express to use my smsParse
middleware when recieving POST requests. The last line tells the express app to be listening on port 1337 of my computer, aka ‘localhost’.
const express = require('express');
const MessagingResponse = require('twilio').twiml.MessagingResponse;
const bodyParser = require('body-parser');
const request = require('request')
const smsParse = require('./middleware/smsParse')
let handleError = require('./middleware/handleError')
const app = express();
app.use(bodyParser.urlencoded({extended:false}));
app.use(express.json())
app.post('/', smsParse, (req,res) => {
// Boiler plate formatting to send a text message.
// res.message has the message saved to it from the smsParse middleware
// we will be sending that to the user who sent an SMS to the number
// Twilio has provided us
let twiml = new MessagingResponse();
twiml.message(res.message);
res.writeHead(200,{'Content-Type': 'text/xml'});
res.end(twiml.toString())
})
app.use(handleError)
app.listen(1337, () => {
console.log('Express server listening on port 1337')
})
The following are the syntax examples of how a user can interact with the app. The first part is what is sent by the user to the number Twilio provides and the second part is the response that would be received.
To get budget data
Groceries // You have $50.41 left in your Dining Out budget.
Enter a Purchase
Groceries -12.34 Wal-Mart // Your transaction with Wal Mart in the amount of $-12.34 was successfully entered.
Enter a Refund
// Enter Credit: Groceries 10.00 Wal-Mart » Your transaction with Wal Mart in the amount of $10.00 was successfully entered.
I setup this middleware to be able to parse the body of the request and determine if the user wants to inset a transaction, or get their budget amount.
const getCategory = require('../helpers/getCategory')
const ynabActions = require('../helpers/ynabActions')
smsParse = async function(req,res,next) {
let numOfArgs = req.body.Body.split(' ').length;
// checks if arguments exist
if (numOfArgs) {
let category = req.body.Body.split(' ')[0].replace('-',' ');
// Only passing in one argument retrieves the balance of that argument/category
if (numOfArgs === 1) {
try {
let data = await ynabActions.getBalance(category);
res.message = `You have ${data.balance} left in your ${data.budget} budget.`
next();
} catch(e) {
next(e);
}
// To Cred or Deb an acct, 3 arguments must be passed
} else if (numOfArgs === 3) {
let args = [...req.body.Body.split(' ')]
let cat = args[0].replace('-',' ');
// converting amt to miliunits (number passed MUST in have two decimal places >> 2.33, -23.45)
args[1] = args[1].replace('.','') + '0'
let amt = Number(args[1]);
let payee = args[2].replace('-',' ');
// checks the argument types, must be correct to continue on
if (typeof cat === 'string' && typeof amt === 'number' && typeof payee === 'string') {
try {
let transactionData = await ynabActions.addTransaction(category,amt,payee)
// saves the response to res.message to be passed back to the user
res.message = transactionData.successMsg;
next()
} catch(e) {
next(e);
}
}
} else {
// Syntax Error Message
res.status(500).send({Error:'Syntax Error!\nFor Balance Inquiry >> Category \n For Purchase >> Category, -Purchase Amt, Payee\n For Refund >> Category, Refund Amt, Payee' });
}
}
}
module.exports = smsParse
I consolidated the YNAB interaction functions to an object with methods inside of it. All of the YNAB interaction happens inside the following script. They interact with that API through the YNAB npm package, rather than making http requests on my own. It is also at this time that I learned about the dotenv NPM package. This is used to store secret keys, passwords, and auth tokens for use in the node.js code. Thanks to Dan at the Acadiana Software Group’s slack page for that!
require('dotenv').config()
const ynab = require('ynab');
const ynabAPI = new ynab.API(process.env.YNAB_ACCESS_TOKEN)
const getCategory = require('./getCategory')
module.exports = {
async getBalance(category) {
try {
let categoryData = await this.getCategoryData(category)
let balance = `$` + ynab.utils.convertMilliUnitsToCurrencyAmount(categoryData.balance,2);
// add trailing 0 to any number with one decimal place
if (balance.includes('.') && balance.split('.')[1].length < 2) { balance = balance+'0'; }
let budget = categoryData.name;
return { balance , budget }
} catch(e) {
throw(e)
}
},
async addTransaction(category,amt,payee) {
try {
let categoryData = await this.getCategoryData(category);
if (!categoryData) {
throw new Error('Category not found!')
}
// Search to see if payee already exists in the payee database
let payeesData = await this.getPayee(payee)
let payee_name;
// If payee does exist, set payee_name to match name in database so a new payee is not created
if (payeesData !== undefined) {
payee_name = payeesData.name;
// if payee does not exist in database, set the payee name to the one entered
} else if (payeesData === undefined) {
payee_name = payee;
}
// define transaction object to pass to the ynab api
let transaction = {
account_id: process.env.FAKE_BANK_ID,
amount: amt,
date: ynab.utils.getCurrentDateInISOFormat(),
payee_id: null,
payee_name: payee_name,
category_id: categoryData.id,
cleared: null,
approved: true,
memo: null,
};
let transactionData = await ynabAPI.transactions.createTransaction(process.env.TEST_BUDGET_ID, {transaction});
return {transactionData, successMsg: `Your transaction with ${transactionData.data.transaction.payee_name} in the amount of ${`$` + ynab.utils.convertMilliUnitsToCurrencyAmount(transactionData.data.transaction.amount,2)} was successfully entered in the ${categoryData.name} category.`};
} catch(e) {
// create custom error based on the returned error from the YNAB api
if (e.error) {
throw new Error(`${e.error.name}: ${e.error.detail}`)
}
throw(e)
}
},
async getCategoryData(category) {
try {
let categoriesData = await ynabAPI.categories.getCategories(process.env.TEST_BUDGET_ID);
// I perform this filter because the API returns an internal master list that doesnt seem necessary to my app
let filteredData = categoriesData.data.category_groups.filter(cat => cat["name"] !== "Internal Master Category");
let categoryData = getCategory(filteredData, category)
return categoryData;
} catch(e) {
throw(e)
}
},
async getPayee(payee) {
try {
let payees = await ynabAPI.payees.getPayees(process.env.TEST_BUDGET_ID);
let foundPayee = payees.data.payees.find(item => {return item.name.toLowerCase() === payee.toLowerCase()})
return foundPayee;
} catch(e) {
throw(e)
}
}
}
One issue I had to tackle was the ability to find the correct Category’s ID, based on what the user inputs into the text. YNAB returns an entire list of categories when you request a budget using the budget ID, but in order to get the specific amount left in a category, you need the category ID and have to make a separate request.
To find the ID, I created a recursive function that would search through category data, and their sub categories until it found the matching category that the user needed. The function returns the category object, which includes data like the amount left in the budget, and other important data points one may be interested in.
let getCategory = function (data, categoryToMatch) {
let category = undefined;
function recurse (data,categoryToMatch) {
for (let i = 0; i < data.length; i++ ) {
if (data[i].name.toLowerCase() === categoryToMatch.toLowerCase()) {
category = data[i];
break;
} else if (data[i].categories !== undefined) {
if (data[i].categories.length > 0) {
recurse(data[i].categories, categoryToMatch)
if (category) {
break;
}
}
}
}
}
recurse(data,categoryToMatch);
return category;
}
module.exports = getCategory
Express handles errors automatically pretty well. After encountering small issues with returning the correct error at certain points in the code, I decided to make my own simple error handler.
To do this, you just need to call app.use(YourErrorHandler)
at the bottom of the express script. In your error handler you need to pass in err
as the first argument, which will be the errors that express has encountered. Here is that code:
function handleError(err,req,res,next) {
res.send({error:err.message, type: err.name})
}
module.exports = handleError;
I really REALLY enjoyed creating and working on this app. The challenge of not working with a browser AT ALL was unique and exciting for me. It helped me to realize how much of our interactions with data and the web are for the most part behind the scenes. It sort of solidified my interest in back-end development over front-end as well. I am interested in hashing this idea out even further to add a bit more complexity to the user interactions, while still keeping it simple enough to not have to go into the YNAB app for every interaction with your budget.
Last Friday (26th) and today (29th) I spent some time diving into the API of my favorite budgeting app. (YNAB!)
I had a random idea Friday morning to explore the ability to create an app that can receive an SMS text message like “Grocery Budget Left” and then return something like “You have $63.00 left in your groceries budget.”
I searched and figured out I would need to use TWILIO or something similar. I ended up creating an account and going through their intro docs and was able to create a very simple example in a couple of hours. What I turned out was just hard coded budget amount that I got the ID for by reading through the YNAB docs. I sent a text to the TWILIO # and got back the amount of my grocery budget.
Read More...