Intrvl is a web app that allows users to create custom interval timers.
Timers consist of named sections, and users can skip to the next / previous sections. Text-to-Speech is also used to give the user the option of having an announcement spoken when the timer starts and ends, as well as announcing the name of each section as it starts. Users can also choose to make a timer public, so that it can be used by others, and users can make lists of their favorite timers (their own or other users' public timers) so they can get to them quickly.
- A full-stack application
- Have complete restful routes for at least one model (GET, POST, PUT, DELETE)
- Utilize an ORM to create and interact with the database
- Minimum Viable Product (MVP)
- Allow users to sign up / login
- Allow users to create / edit / delete timers
- Allow users to view all their timers and use each
- Use a Text-to-Speech API to announce when a timer is starting and ending
- Stretch Goals:
- Allow timers to be repeated
- Allow timers to have 'sections'
- Allow sections to be rearranged
- Allow sections to be repeated
- Allow users to add tags (categories) to timers
- Allow user to set some timers as public, and others as private
- Allow users to have timer section names be announced when that section starts
- Styling
- Use TailwindCSS
- Responsive design
- Dark mode
- Allow users to duplicate an existing timer to use it as a template
- Live Site: Intrvl.
- Original Project Pitch and Wireframes: Github README
- Backend: NodeJS, PostgreSQL, Sequelize, EJS
- Frontend: TailwindCSS, Flowbite, JavaScript
- Web Speech API
- The timer functionality was built with JavaScript, and relies on a fixed time-step engine, built for my previous project Bodhi's Dreamworld.
- Text-to-Speech relies on the native web speech synthesis API: SpeechSynthesis
- The app interacts with the PostgreSQL database through the Sequelize ORM
- Views are built with the Embedded JavaScript (EJS) templating language, while styling was done with TailwindCSS (a utility-first CSS framework) and Flowbite (an open-source library of components built with TailwindCSS). Ionicons (open-source icons library) was used to add SVG icons. Typed.js was used for a text animation on the home page
- Winston and Chalk were used for logging. Winston allowed the creation of different logging outputs, depending on environment logging level. For example, errors are logged in a separate file, so they can be easily found, and in development all levels of logging are output to the console.
- connect-flash was used to show flash messages to users upon redirect, to let them know if an action succeeded or to inform them why an action had failed.
- To run this app, clone it (or fork and clone) and follow these steps:
- Install dependencies using npm
- Create a
.env
file and add an encryption key - Create the database
- Start the app in development mode, by running
npm run start:dev
npm i
echo "ENC_KEY=[Random Key]" >> .env
sequelize db:create
npm run start:dev
HTTP Verb | URL Pattern | Action | Description |
---|---|---|---|
GET | / | Index | Describe app, show nav links |
GET | /login | New | Show login form |
POST | /login | Create | Log user in, by setting a cookie |
GET | /logout | Destroy | Log user out, by deleting a cookie |
POST | /favorites/:timerId | Create | Add timer to favorites |
DELETE | /favorites/:timerId | Destroy | Remove timer from favorites |
GET | /users/new | New | Show form to sign up |
POST | /users | Create | Create a new user |
GET | /users/:id | Show | Show user profile and their timers |
GET | /users/:id/edit | Edit | Show form to update user profile |
PUT | /users/:id | Update | Update profile / credentials |
DELETE | /users/:id | Destroy | Delete user's account |
GET | /users/:id/favorites | Index | Show users favorites |
GET | /timers | Index | Show all public timers |
GET | /timers/:id | Show | Show a specific timer |
GET | /timers/new | New | Show form to create a new timer |
POST | /timers | Create | Create a new timer |
GET | /timers/:id/edit | Edit | Show form to edit a timer |
PUT | /timers/:id | Update | Update a timer |
DELETE | /timers/:id | Destroy | Delete a timer |
POST | /timers/:timerId/sections | Create | Create a new timer section |
PUT | /timers/:timerId/sections/:sectionId | Update | Update a section |
DELETE | /timers/:timerId/sections/:sectionId | Destroy | Delete a timer section |
POST | /timers/:timerId/tag/:tagId | Create | Associate a tag with a timer |
DELETE | /timers/:timerId/tag/:tagId | Destroy | Remove association between a tag and a timer |
GET | /tags/new | New | Show form to create a tag |
POST | /tags | Create | Create a new tag |
GET | /tags/:id | Index | Show all timers associated with a tag |
GET | /tags/:id/edit | Edit | Show form to edit a tag |
PUT | /tags/:id | Update | Update a tag |
DELETE | /tags/:id | Destroy | Delete a tag |
- This project allowed me to strengthen my skills in planning and designing an Entity Relationship Diagram, and then implementing that design
- I also learned how to use the SpeechSynthesis API. I had originally planned to use an external TTS engine, such as the Google Cloud Text-to-Speech API, however the complexity of needing to generate the speech on the backend, store MP3 files, and then send those to the client, seemed unnecessary given the widespread browser support for speech synthesis. In the future, I plan to continue building on this project by allowing the user to select the type of voice they would like to use, as well as the pitch, tone, and speed.
- This project allowed me to continue practicing my skills in creating interactive elements in JavaScript and HTML. Building the timer component and allowing users to skip forward / backwards through timer sections was a fun challenge.
- I learned how to use throttling for better performance. In the view showing the timer, there is a function that fires on window resize events, to ensure the timer sections are displayed at the correct width; to ensure this function isn't called too many times as the user is resizing their window, throttling is used so that it runs at most every 500ms.
- I learned how to use Chai and Mocha for testing, along with supertest and FakerJs. I have tests for the user model, for example, which ensure the methods for hashing and verifying passwords are working correctly. I had originally planned on using a Test Driven Development approach for this project, however, considering that this is a monolithic full-stack application and the routes directly send HTML to the client (rather than JSON), the complexity of learning to test views (essentially performing integration testing, rather than unit testing) in the tight time frame of this project was too much. In the end, I only wrote tests for a few models.
- On the design front, I learned about Neubrutalism and tried to use this style for the app.
- Learn Debounce And Throttle In 16 Minutes
- Flash Messages in NodeJS
- SVGOMG - SVG Optimizer
- Regular expression to identify a BCrypt hash
- Cheatsheet: Write Good Tests with Mocha
- Testing with Sequelize
- ExpressJS and Sequelize application tested with mocha, chai, supertest, migrations and seeds
- Neubrutalism is taking over the web (Article)
- Neubrutalism is taking over Web Design (Video)
- Linkedin - Stefan Vosloo
- Twitter - @saulthebear