Now that we've installed everything we need to in order to create a service and deploy it online, we're going to design and create the main project that will teach you everything I promised to teach you.
Let's go over what we will actually be building. Because I am fed up with coding interview questions, in-class examples, and coding assignments that have no point, I won't be contriving a project for you. Instead, we are going to be building something actually useful (i.e I needed to use it): A web app that shows the user a "Contact Form" and emails the things that they filled out to the make believe company's email. (Okay it's a little contrived, but in my case, I actually had a company I was making this for)
Here's what it'll look like:
This is a screenshot from my company's website (Have a web project? Contact us!). Anyone interested in connecting with us would ideally fill out this form, not be a robot, click submit, and see a confirmation message that their message was sent. Then, we see that message in our inbox.
Because then our email gets picked up by web scrapers and we get spam. This hides the email, and lets us change it internally.
I was really into architecting actual houses and things in high school before I switched to code. But that's irrelevant.
To get started with creating an app like this, we need several moving components. Let's think about what they are and then translate that into a plan for our software.
To begin, the most obvious thing we need is the User Interface. This is what the user will be able to see and interact with, and we need to make sure it's nice, pretty, and usable. This is the part of the application we call the front-end. It's on the "front" of the application because that's what the user sees and interacts with. This front-end is an app in itself, deployed to the internet and generally accessible by the outside world by going to a URL.
If the application was a car, then the front-end would be the steering wheel, pedals, buttons, gauges, interior detailing, leather on the seats, etc. It's the pretty part that works well and is responsive to the users' demands.
Aside: It's not too hard to take a simple prototype of something and make it look a little nice with CSS. We'll do a little bit in this project, but I suggest reading some articles on CSS and designing websites with it. Here's a great starting point: Learn CSS in 5 minutes
A lot of times, a little CSS in your prototype can mean the difference between a client/boss accepting your idea or rejecting it on the basis of ugliness and "no one will like it."
The next thing we need is a way to send what they filled out to our email address. Luckily, the solution to this problem is basically this whole tutorial. Here's the plan: we're going to send the data they filled out on the contact form through the pipes that make up the internet, to an app that we write in Node.js. This app needs to be live so that we are able to access this app at a URL. Our front-end code can send requests to that URL and data along with those requests. (Hence the previous part of the tutorial).
Our back-end app will accept the data over the internet, make sure it's all nice and safe (for the most part) and then actually send the email. Because sending and receiving emails is a whole big task in itself, we will be using a service called SendGrid to send the email for us.
This is intentional because I don't want to write an email service, and using this will let me show how to use a third-party library in our code.
I'm actually appalled that it takes students (at least in the set of classes I took) until their third year upper levels to learn that it's okay for people to use other peoples' code not not get points taken off for using a library. smh.
SendGrid is a service which makes sure that the emails get delivered, provides tracking for emails, tracks whether they were delivered, and more. Our back-end will talk to the SendGrid service in a similar fashion to how our front-end talks to our back-end. (Sending data over the internet).
Looking at this huge wall of text, I'm lost too. So I'm going to draw a picture for you. Because no architecture is truly designed until there's a drawing with boxes and lines.
+------------+ | | +-------------------------+ | SendGrid | | <----------------+ (External) | | Node.js Backend +----------------> +-----------+ | | +------------+ | +-------^-----------------+ | || +------v------+ || | | || | Our Inbox | || | | || +-------------+ || || || || || || +-----------------------+ |-----------> | +-----------+ React Frontend | | | +----------^------------+ || || || +----------v----------+ | | | User's Computer | | | +---------------------+
(ASCII art because we are c o o l )
Each box is its own application/service, and each connector is a pipeline of communication along which data is sent. These boxes are the "moving parts" that make up our app.
- the user uses their computer to send requests to the front-end, filling out a form.
- The front-end sends data in a request to the back-end, and if input is safe, the back-end accepts, and sends a request to SendGrid.
- If the email was sent, SendGrid sends back a "Success" response (remember that
- The back-end sees this success and sends a "Success" response to the front-end.
- The front-end in turn notifies the user that their message was sent to the company. If anything goes wrong, the back-end would be sending non-success messages (of many possible varieties like 401, 404, 500, etc) and the user will see an error message telling them to try again.
This is the aspect of the job that I really struggled with when I was learning all these for the first time. The answer is you aren't, and you probably never will right off the bat. I called this section "Designing our web app architecture" because that's exactly what we are doing. Designing a solution that does what we need to. Like any design, we put thought into it, explore our options, change aspects as we get more information and as we plan for contingencies and future features.
However, there are some starting points. There are several architecture patterns that are used in industry and we will talk about two of them here. For one, the architecture we are using here with a single front-end, a single back-end, and that's all we need. After all, by modifying the backend code, we can do much more than the singular task of sending emails. We could add login/logout so users would have to be logged in to send messages. We could add a blogging feature so that users can publish posts and have others see. Suddenly, we have a blogging platform with a technical support contact system.
The architecture we have designed follows the the Monolith pattern. This is because the back-end is a "Monolith" as in it does everything our app needs to in one place, in one codebase. All functionality we might build in would be built into this back-end.
For a long, long while, this is how apps were built. It was simple, it could be scalable, and it was easy to follow and reason about. However, there are some problems with this that you might already be able to see.
If everything is in one codebase, adding or changing something in one place in the code, such as the login/logout authentication logic, might inadvertently break something completely irrelevant that is somehow dependent on the authentication logic.
To contrive an example, our hypothetical blogging system might be using a variable declared in the auth system to figure out who to set as the publishing author in a blog post, and that variable might have gotten refactored away into the ether in the last code merge. Now, the blogging system is broken, and the blogging platform team has to spend time fixing something that could have been avoided. To avoid this brings us to the next architecture pattern:
If you've been reading developer blogs, looking at software headlines, or been in a meeting with someone obsessed with buzzwords, you might have heard about microservices. A microservice is just that, a small service. Our back-end that we just designed into our application does one task: take in contact form content, and trigger an email send. Now imagine that all functionality in our application is broken down into components like that based on the task they achieve, and written into separate codebases and deployed separately, and you've arrived at a Microservice Architecture.
Your authentication system might be its own app, deployed to a large, powerful server on AWS, because it needs to handle traffic from a lot of people logging in and out.
Your blogging system would be its own service, deployed and scaled on an even more powerful server, with several copies of the app running to handle the mega traffic from everyone, including trial users who don't need to log in.
Your contact form (from this tutorial) would be it's own thing, deployed to a smaller, less expensive server, since it barely sees any traffic compared to the other components.
Each app has its own concerns that it's worried about, and as long as the team works to keep the data sent between the services consistent, a team could completely rewrite one component and redeploy it, even in a wholly different language, and the system as a whole would still work uninterrupted.
That is the ideal, anyway, but like in everything, the real world (read: real teams) isn't perfect so communication and knowledge share remain key in keeping everything working.
Microservices are the new more-and-more common method of doing this, and a lot of companies are making a transition from their legacy systems into this. (This usually also coincides with a move from mainframes and data centers into cloud service providers like Amazon Web Services or Google Cloud Platform. These providers make things like microservices easy to manage.)
Aside: Microservices, their differences and similarities to Monoliths, and how to design one, is great knowledge to have and is very impressive to recruiters if you're looking for internships while in college.
Here is the essential talk on microservices (in my opinion).
Your homework to close out this part of the tutorial is to watch that video. I even bolded it so you'll see it.
Monoliths and Microservices aren't the only way to do things. Feel free to come up with your own completely new and better paradigm and then write a blog post and send it to me so I can learn about it.
When developing a new application, script, or anything, it's good to start with an "MVP" or "Minimum Viable Product." (Note: this is all about the way I personally use "MVP" in both meaning and practice, and company or "official" convention may be slightly different)
A minimum viable product is simply that. A small, rudimentary proof-of-concept or initial feature-set of whatever you are building. It is seen usually as the first step and takes less than a day to complete, leaving time for discussion with your team. In practice, I usually use it as a term to define what it means to "start" working on the application. For this project, it would mean setting up an empty React project, making the deployment that we made in Part 1, and achieving communication between the two.
You can start with an MVP, then work in an iterative way to get to where you want to be. You break down your goals into small steps, then boil those steps into easily digestible tasks, then split those tasks into individual action items you can assign to people to complete. Completing each one slowly adds to the product, slowly but surely completing the application as each task is implemented and merged into the initial codebase from the MVP.
This is a great way to get some scope, and lose the fear of starting when you are faced with creating something complicated or "big." After all, by implementing the MVP, you already have code pushed, and you are simply adding things one by one.
This pattern of having an MVP upon which all future features are added to will also help us practice Continuous Deployment. You may have seen the term "CI/CD" floating around, which stands for Continuous Integration and Continuous Deployment. Combined, this is the practice of merging developers' code changes into the master several times throughout the day (Integration) and also deploying new versions of the code live as those changes are made (Deployment).
This is a useful practice for us, because as we complete features one by one, we can push each one live as they are completed. In effect, this means that at any stage during development, there is a live working version of the application, and it is (theoretically) never broken. This is common practice nowadays and there are many tools to automatically run tests, deploy, and notify developers on errors as changes are pushed into the codebase. Here is an in depth article from Atlassian explaining the concept in more detail.
Part 3 of this tutorial will be creating an MVP for our app, and also a small (lengthy) aside to explain some more fundamentals that will help make Parts 4 and 5 make more sense. We will then go into Parts 4 and 5, building the back-end and front-end respectively.