Skip to content

kimfucious/cowsay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Multi-Tier Cowsay

 ________
| Hello! |
 --------
      \   ^__^
       \  (oo)\_______
          (__)\       )\/\
              ||----w |
              ||     ||

What is this?

This repo demonstrates:

  1. Realtime development, using live Docker containers
  2. GraphQL access to a backend API
  3. JSON web token authentication
  4. Persistent data storage, using MongoDB

This repo is comprised of two main directories:

  1. cowsay-app is a front-end React web application
  2. cowsay-api is a backend Express API, using express-graphql

A third directory, mongodb_data, is required for the persistant storage of a MongoDB database

🐮 You need to create the mongodb_data directory and give it the correct permissions. See Setup step 3 below.

Prerequisites

  1. Docker
  2. Docker Compose

All services are provisioned via Docker, using Docker Compose.

Setup

  1. Clone this repo to the directory of your choice:
git clone https://github.com/kimfucious/cowsay.git
  1. Change into the newly cloned directory

  2. Create mongodb_data directory and set permissions on it:

🐮 There is no initial database. You need to first create a new directory mongodb_data and set permissions on it, like so:

mkdir mongodb_data \
&& sudo chown -hR 1001:1001 ./mongodb_data/

Get things running

Run Docker Compose

  1. After you've done the above, navigate to the root of this cloned repo
  2. Run docker-compose up
  3. The first time run will setup MongoDB. You should see something like the following amidst the output:
mongodb_1  |  18:02:30.89 INFO  ==> Deploying MongoDB from scratch...

MongoDB is up when you see waiting for connections on port 27017

🐮 If you see a MongoDB timeout error, search the log for the following:

db     | mkdir: cannot create directory '/bitnami/mongodb': Permission denied

Most likely, there's a problem with that directory and/or it's permissions. See step 3 in Setup above.

Notes

  • Docker-compose will pull images, build them, and run them in three discrete containers (this can take a while on the first run):

    • The web app will run on port 80 at http://localhost
    • The api will run on port 4000. You can access graphiql at http://localhost:4000/graphql
    • The database will run on port 27017. You can access it, using Compass, with mongodb://user:secret@localhost:27017/cowsay?authSource=cowsay

    This is all configured in docker-compose.yml and the Dockerfile files in the app and api directories.

  • Changes made to the app or api code will restart the respective services within the running containers on save, thanks to nodemon. Changes to npm packages will not work without rebuilding the affected Docker image. See here for details.

  • The db gets created on first run of docker-compose, but again, you need to ensure that the file permissions are set correctly on mongodb_data, as just stated, in order for things to work. See note here for details.

  • An initial db root and admin user (user) are created on the first run of docker-compose. These are set in the .env file in the root directory of this repo.

🐮 Subsequent starts will not create db users, if/when you change environement variables! If you want to do that, rebuild the database. See here for help on that.

  • An initial web-app admin user, [email protected], is created when the API is first run (when there are no users). The initial password is set to Passw0rd123. These can be changed in cowsay-api/src/start.js.

  • Env files are found in each directory, including the root directory. These files have been intentionally not excluded from this repo, but should be, if/when you ever fork a copy of this repo for anything going forward. See here for details.

  • Yes, the Auth0 client secret has been rotated; thanks.

  • The app.listen() and mongoose.connect() functions have been separated out of app.js and placed in start.js so as to separate facilitate testing.

  • The token authentication is "manual", for demonstration purposes, rather than using a third-party solution, though I may add something else later.

  • To test the API using graphiql, temporarily comment out the authorization lines in cowsay_api/src/graphql/resolvers.js, like this:

getCows: (args, req) => {
  // if (!req.isAuthorized) throw newError(403, "Forbidden");
  return cows;
};

Other Stuff

Rebuilding Docker images

If you add npm packages to the app or api, these changes will cause the apps running in their containers to fail, because they are relying on an old verions of package.json.

To get them running again, do the following:

  1. Optional - run Docker with the prune option:
docker system prune
  1. Run Docker Compose with the build option:
docker-compose up --build

Rebuild the database

If you want to change the user configuration of the MongoDB database, it's easiest to just delete the db and rebuild it.

  1. CTRL-C in the terminal where your ran docker-run-compose to stop all running containers
  2. Delete the mongodb directory inside the mongodb_data folder without deleting the parent directory. Use sudo rm -rf mongodb_data/mongodb/, as permissions on folder aren't yours.
  3. Change the .env variables in the root of this repo as desired.
  4. Run docker-compose up

Things learned and/or to be done differently

  • Clean up unfinished async calls in React useEffect hooks, like this.
  • Debouncing form value validation is interesting, but probably not worth the complexity. I prefer Formik and onBlur with Yup.
  • While homespun authentication is good to understand, I think others have already built better wheels. I like Auth0 and would implement a more sophisticated approach with both Access and Refresh tokens.
  • GraphQl error handling with express-graphql is not fun, I'll probably use Apollo on future projects, as I've read that it deals with this better.
  • If I were to deploy this to production, I'd probably use AWS Fargate, but before I did that, I'd consider converting the API to a serverless Lambda function and serve it up using AWS API gateway.
  • It's nigh impossible to prevent password managers, like 1Password, from grabbing at your form fields.
  • Using the useReducer React hook for state flow is doable, but I'm thinking that going full Redux would be better when working with lots of actions and/or more complex apps.