Over!

Node Knockout is over! Time to pause for a moment and summarise, what I learned this year.

What is Node Knockout anyway?

Node Knockout is a hackathon. It is online only and lasts 48 hour. Basically the rules demands from you to use Node.js and play fair.

Our project

The topic for us was CaIT - or Cards against IT. It's a spin-off of Cards Against Humanity.

This year I had the pleasure to work with a friend of mine again, Richard Armuelles from Panama and a fellow Mozillian.

Since I use this hackathon to learn a lot inn a short amount of time, I left the choice of our tech stack to him.

The tech stack

Since

But I don't want to use Express...Express is like the Phpmyadmin + Wordpress/Joomla stack for Node.js xD

we went with hapi this year. Luckily they provide a express to hapi guide.

We both need to learn React, so this was a quick decision. For the UI, we went with Reactstrap, which wraps Twitter Bootstrap components into React ones.

How do they talk to each other? We decided to go hipster and put GraphQL on top. To balance that out, the database became PostgreSQL with sequelize as ORM and everything running on Heroku.

Updates on organisation

I noticed, that some things changed with the organisation.

It used to be, that you get some discounts on services integrated with Heroku. This year, it was more of a Bring-Your-Own-Infrastructure. But you should be able to grant them permissions into it.

Using Heroku

Anyway, Heroku is easy to use. I went shopping on their addon page and picked:

Directory structure

As mentioned, we went with a server+client setup. So one of the first actions was to create one directory for the server development and one for the client. Within the client directory, we used create-react-app for bootstrapping a React app. At this point, the code looked like this:

.
├── client
│   └── cait
│       ├── package.json
│       ├── public
│       │   ├── favicon.ico
│       │   ├── index.html
│       │   ├── logo192.png
│       │   ├── logo512.png
│       │   ├── manifest.json
│       │   └── robots.txt
│       ├── README.md
│       └── src
│           ├── App.css
│           ├── App.js
│           ├── App.test.js
│           ├── index.css
│           ├── index.js
│           ├── logo.svg
│           └── serviceWorker.js
├── index.js
├── package.json
├── Procfile
├── README.md
├── server
│   └── index.js
└── todo.txt

Getting a server running

Since I was waiting for Richard to setup the server I wrote a minimal HTTP server to be able to take a look at the React UI.

Yes, create-react-app comes with an own server. Which does not update for me on file changes. If you interrupt the process with Ctrl-C, it doesn't terminate so that the next time you run it, the port is still in use. Two or three of them made my laptop cringe. htop to the rescue, so I could send a SIGKILL (SIGTERM had no effect).

I'm not happy with create-react-app. Even simple stuff, like changing the <title> feel like a hack in this setup.

Then I picked up the above Heroku apps and added the configuration. Meanwhile Richard pushed the Hapi-Server - and Heroku exploded.

Looking into the logs, we realised a communication problem: yarn's yarn.lock and npm's package-lock.json can't exist both. Well they can, but Heroku refuses to work. Since I'm not using yarn, we went the npm route.

Surprises on Heroku

After porting the configuration over to hapi, we could tackle the next step: Setting up GraphQL.

The next surprise was looming for us shortly after: The server was not coming up on Heroku. A quick look into the log files revealed, that Heroku did not installed the dependencies of the server!

Since we don't commit node_modules, another way was needed. My idea was to run a small node script to walk into the respective directories and npm i them. Nothing happened. Something which would bug me a lot over the weekend: For some reason, access to the file system is heavy locked down. So your calls to it will simply do nothing (not even throw an error or giving back control).

Luckily I discovered heroku-postbuild documentation and could make it work.

Connecting to PostgreSQL

Cool, the server works now! What next? The database would be needed.

Which was another adventure. I could access the Heroku PostgreSQL with psql - but not with Sequelize. Trying to establish the connection by calling its .authenticate() caused an error message.

I couldn't make a sense of this. So I took to DuckDuckGo and searched for ideas since we were surely not the only one running into it.

So Brandon Evans mentioned in Best Security Practices for Amazon RDS with Sequelize, that you could configure a PostgreSQL server to reject insecure connections. By default a client is running in a mixed mode.

Bonus: Disable Insecure Connections

Using SSL/TLS is great, but if other database clients don’t follow suit, you still have this security vulnerability. Fortunately, you can configure your database to reject insecure connections.

For PostgreSQL, we simply need to add a parameter group that has the setting rds.force_ssl set to 1. After application, if you attempt to connect to the database insecurely using Sequelize, you will get the following error:

Unhandled rejection SequelizeConnectionError: no pg_hba.conf entry for host "xxx.xxx.xxx.xxx", user "pguser", database "pgssltest", SSL off

Ding! So how do I tell Sequelize to use TLS? Luckily the documentation makes a hint for a flag for PostgreSQL. However, the config shows a second one, so I set both to true and — could connect!

Wiring up

Yay, so now wiring everything together?

For Hapi.js there is a plugin called graphi, which adds functionality to deal with GraphQL requests.

What I like is, that it also allows to decorate a ReST endpoint. So I could start with plain ReST calls and switch them out with GraphQL down the line.

So /graphql was responding with data, I could populate the database and describe a schema.

GraphQL with React

I put focus on React next, so I could make the stitch between client and server next.

First, I tried to use Apollo's client. So wrapping my App with a abbr and refactored a bit of my code to easier access the Redux store. But I struggled to wrap my head around how to trigger the AJAX call. I mean, I can't use fetch() or axios, right?

Except I can!

The article series was a blast in explaining how to achieve this! I actually like, that I could put away with some magic helper functions and go with template strings and object destructuring assignment.

In a refactored setup, I assume it would be even possible to share the schema (or is it called type definition) between client and server. I mean, it is a simple (template) string…

Then you pass it as an additional argument to axios.post(), where the key of the object matches the kind of operation you want to execute (query or mutation), as well as variables, which get interpolated.

import axios from 'axios';

import * as pkg from '../package.json';

let baseURL = 'http://localhost:8080/graphql';
if (process.env.NODE_ENV === 'production') {
  baseURL = pkg.homepage + 'graphql';
}

const axiosGraphQl = axios.create({
  baseURL
});

const CARD = `
  query {
    card(text) {
      selected
      type
      text
    }
  }
`;

const ADD_CARD = `
  mutation ($text: String!, $type: String!): {
    addCard(text: $text, type: $type) {
      selected
      type
      text
    }
  }
`;

const addCard = async ({ text, type }) => {
  const result = await axiosGraphQl.post('', { query: ADD_CARD, variables: { text, type } });
  const { data: { data: { addCard } } } = result;
  return addCard;
};

A bang after the type (e.g. String!) means, that the type can not be null. So with that working now, I could … refactor. This time my Redux state. Right now we are eagerly loading at the beginning of the app. In a real scenario you would do that on entering of the view, I guess.

Redux

While I'm at refactoring, I could use Redux Router as well. We have several potentials views to enter:

  • A landing page
  • An user page (we planned to allow login with Twitter or GitHub)
  • A page for building your deck (and suggest more card texts)
  • A page for actually playing a party

Redux Router is overkill for that. But hey, we were in for the learning! Its usage is quite declarative, so I won't go into the details here.

For asynchronous calls, you normally want to use redux-thunk, but I was too lazy to wire up my calls etc. So I just updated the state when my asynchronous call came back.

If you develop with Redux, it is helpful to have the Redux Devtools extension installed. This way you can inspect the state at any given point in time.

A bit of game logic

I felt pretty excited, that I could make an actual game this year! I mean, in the former submissions, I managed to crash the application shortly before the deadline :-/

Here, the game logic is mostly contained on the party side. For starters we required to have at least one question and answer each.

I couldn't resist to make a reference on the answer to participate in this year's Node Knockout.

I had to absolutely implement it! Twitter is converting your GIFs to (silent) videos for performance reasons. Luckily someone built video-react so we could show the video. Here I noticed, that Firefox Enhanced Tracking Protection is blocking the video from playback (next to your adblocker).

Anyway, once the video played, we would hide it (for some reason did not work when you refreshed on the page itself) and show you your deck.

Your opponent would have all the other cards ;-) Ideally, we would have needed to limit the number of cards (and therefore make him somehow pick some).

Then the game checks whether the next card is not from the previous player and of a different type. Once played they get removed from your hand.

I worked until the last minute on a multiplayer variant (with different players controlled by the computer), but couldn't get the game logic extended. Phew, I had that in a feature branch ^^°.

Our design spec imagined something similiar to Hearts as UI. But alas, time ran out.

Instead, we added a small easteregg, which was sadly blocked on Heroku because of CORS issues.

Conclusion

As usual, we had too much on our agenda and were thrown off by a database. But hey, it was running at least! I'm super happy with my result.

I mean, from the tech stack above I only used Sequelize and PostgreSQL before.

Speaking of usage, I certainly won't use create-react-app ever again. The setup is such a drag, that I rather spent time copying over a webpack config (or better: rollup) and get going. I mean, you loose everything once you „eject” its configuration! Who comes up with that?!

Next thing I learned: I won't use GraphQL in the frontend for such small apps. The dependencies bill is too high. What I did here would have been easily be doable with a few ReST endpoints as well (but that wasn't the point).

For Sequelize I'd prefer more an approach in the direction of comments-api, so to not lock myself in.

I think, I'd need to play more around with Hapi - or another of the top 10 frameworks to pick in 2020, since it could have some interesting ideas.

Hopefully there will be a next round of Node Knockout. The participation was on an all-time low, Twitter's nodeknockout2019 almost silent and some sponsors (like Heroku) left. Plus, it ticked me off, that many links (on the website, in the emails) were HTTP (i.e. insecure). We have 2019. Let's Encrypt cost you nothing.

Anyway, I'd participate next year as well. When else you can learn loads of technology in 48 hours? (Hopefully I won't forget the vote button).