Angular 2 with Socket.IO Beer Tasting Party Study Application (Part 1)
In this multi part blog series I will be building an Angular 2 application with Node using Socket.io as a realtime communication facilitator.
My learning process has always been such that, I must use a technology for some practical purpose to have a good working knowledge base from which to pull if I plan to use it for production applications. Please be advised that this tutorial is a study meant to give me a chance to work through problems, not necessarily one you should use as a guide for production applications.
First the premise.
My wife and I are hosting a blind beer tasting party where we will serve beers and people will comment and rate each one anonymously. At the end of the event all of the beers will be revealed and people can match their notes up with the beers. This presented the perfect opportunity for me to put to use Angular 2 and learn some of the ins and outs while producing something useful that I can get user feedback on right away.
My application vision.
My vision for the application is that it should start with the use case of the host. The host will start a new tasting session and give it a name. The host will then manifest each beer by supplying the name and company/brewery of each. The app will give the host a number to assign to each beer. The host could put each beer in an opaque bag and put the number of the beer on each bag. This way, hopefully each beer can remain unknown to even the hosts until the end. Once each beer has been added to the tasting manifest, the host can start the tasting session.
During the tasting session the app will dictate in which order to serve the beers by randomly picking a beer from the manifest. The host will serve the beer to all participants. During the session, all participants will see the a new notes screen where they will rate the beer (1 to 5), supply a tasting description, and take a guess as to which beer from the manifest it is. Once the round is up, the host will move on to the next beer, and participants will not be able to change previous notes.
At the end of the tasting session the application will freeze all participants notes and reveal which beer is which on each participants note screens. The participant with the most matches will be the winner!
Technology decisions, decisions.
My aim is to use this project to learn Angular 2 and not make it extremely complicated by using other technology I am not familiar with. I have chosen MySQL as the database technology because I am familiar with it, and the data will be highly relational. I will use Express on the server side to handle all of the standard requests. I will add Socket.IO on top of express to do the real-time communication like updating the host when everyone is done with their tasting notes, etc.
Tech Stack Overview
- Angular 2, for routing, views and abstraction of model logic.
- Data service abstraction
- Serving static files
- Handle standard HTTP requests
- Realtime communication and updates between clients and server
- Durable session data
- Easy relational abstraction
The data model.
First things first, I am going to list out all of my objects and their relationships based on the overview I gave earlier. I will build my database first as that will drive the flow of the application by handling relationships for me.
Tasting sessions are the core object with which everything else will relate. Lets build that first. I want my tasting sessions to have a name, date and status (open or complete). The tasting session encompasses the whole experience of the application. Pull up your favorite MySQL interface and let’s get crackin’. If you don’t have it installed, it’s pretty easy to do so.
CREATE DATABASE `beer_tasting_app`; CREATE USER `beer_admin`@`localhost` IDENTIFIED BY '_B33rZ!_'; GRANT ALL PRIVILEGES ON `beer_tasting_app`.* TO `beer_admin`@`localhost` IDENTIFIED BY '_B33rZ!_'; FLUSH PRIVILEGES; USE `beer_tasting_app`;
Now create the simple sessions table.
CREATE TABLE `sessions` ( `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `name` VARCHAR(300) DEFAULT NULL, `session_open` TINYINT(1) DEFAULT 1 ) Engine InnoDB;
Next important object that our use cases are driving are users. Our users will have a name, and type. There are 2 types of users ‘host’ and ‘tasters.’ When the application first fires up, it will check to see if there are any sessions open. If a session is not open, the first user to claim the host role will be marked as the host and no other users in the session will be able to access the host functionality. The tasters are the standard user type, when a session is open, they will be presented with the review interface where they will supply their tasting notes, best guess at the beer, and rating. Because this is a party application and we don’t need to support persistent user logins or users that last more than a session, the user to session ratio will be 1 to 1.
CREATE TABLE `users` ( `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, `session_id` INT(10) UNSIGNED, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `name` VARCHAR(100) DEFAULT NULL, `user_type` SET('host','taster') DEFAULT 'taster', FOREIGN KEY (`session_id`) REFERENCES `sessions`(`id`) ON DELETE CASCADE ) Engine InnoDB;
Arguably the most important object is the beers. Beers will have a name, a 1 to 1 relationship with a session and a unique identifier assigned by the application level that will be hard for our guests and hosts to remember when they are bagging and manifesting the beers. This table will also record if the tasting is currently in process for a beer. Combined with the session status, we’re getting a pretty good idea about how we will track the overall state of the application!
CREATE TABLE `beers` ( `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, `unique_code` VARCHAR(6) DEFAULT NULL, `session_id` INT(10) UNSIGNED, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `brand` VARCHAR(100) DEFAULT NULL, `name` VARCHAR(300) DEFAULT NULL, `type` VARCHAR(300) DEFAULT NULL, `round_number` INT(5) DEFAULT NULL, `tasting_in_process` TINYINT(1) DEFAULT 0, `tasting_complete` TINYINT(1) DEFAULT 0, FOREIGN KEY (`session_id`) REFERENCES `sessions`(`id`) ON DELETE CASCADE ) Engine InnoDB;
This table object can be thought of as a linked relationship between the users and the beers. Tasting notes will record a unique note, rating and guess for each user for each beer. This table is where the action really happens!
CREATE TABLE `notes` ( `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, `beer_id` INT(10) UNSIGNED, `user_id` INT(10) UNSIGNED, `beer_guess` INT(10) UNSIGNED, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `rating` TINYINT(1) UNSIGNED, `notes` TEXT, UNIQUE KEY `user_x_beer` (`beer_id`,`user_id`), FOREIGN KEY (`beer_id`) REFERENCES `beers`(`id`) ON DELETE CASCADE, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ) Engine InnoDB;
The obligatory object relationship map.
In Part 2 we will:
- Build out the database access layer.
- Build out the models.
- Write a unit test suite for the models.
- Begin to build out the API.