In the second part of this series we looked at the most basic things we could do - package setup, some basic scripts, common integrations etc. And we left it for this third article to cover actually setting up a real application which could do some minimal things, and for the next one to cover infrastructure and making the project public. It turns out there’s a bunch to cover here as well, and one article won’t do it justice. So I’ll only cover the initial application behaviours, a bit of testing and more integrations here, and we’ll delay things like sessions and authentication for the next part.
The current file structure is:
This is a bunch bigger than the one from last time. New additions are the
test-e2e directory, the
migrations one, and a bunch more source and script files.
The big thing is setting up NestJs. In broad lines, this is a web framework in the spirit of Ruby On Rails or Django, but taking a lot from Angular as well. So more “batteries included” than “do your own thing”, like Express is. Though it is still using Express under the hood. It’s all built in TypeScript, so it has nice types and intense support for annotations. It also has a bunch of sane defaults, but it doesn’t go as far as requiring a particular ORM or view engine. In fact, the biggest thing it enforces is its own Dependency Injection framework.
index.ts file has become:
It’s purpose is simply to start the server and do the “central” configuration via the
MainModule. Here’s where we’ll list all the other modules of the application, and setup the middleware, logging, error handling etc. which we’ll need to do. Right now it’s pretty bare bones though.
In order to actually do something you need to run
yarn serve or
./scripts/serve.sh (or, for development
yarn serve:watch). This is essentially
ts-node src/index.ts. This will start the serving process and after that you can start hitting the server with requests
curl localhost:10001 will show just a
Hello World type message. There’s only the index route for now, but we’ll need a bunch more as soon as the next post even.
A thing to notice is the
ConfigModule, which is a provider for a
Config object. Check it out in config.ts, cause it’s a bit big to put inside this post. Best practice is to separate application’s configuration into a separate file - and provide different values for different environments. 12 factor apps suggests using environment variables for example. We’ll use the
env.$ENV files in the
./config directory for holding configuration information. There’s currently values only for the
test (ie Travis) environments, but
live will follow once we get there.
The file looks like:
Config is responsible for translating this into something the application can work with, like
config.env. It also does a bit of “parsing” and checking, though I’m not 100% happy with how it’s done. Perhaps Raynor can help here as well. The way it’s meant to be used is that classes which need some config value will inject
Config in the constructor and Nest will take care of providing it. Makes testing a lot easier and also clearly marks the dependencies of your code.
setup-env.sh script is called at the start of every other script in
./scripts now. It’s purpose is to write an
.env file at the root of the project which links to the environment specific version of the config file. This is for use by third-party tools which expect that file to exist in a certain place, or at least be the same across environments (looking at you
As mentioned in the first post, we’ll be using knex.js as an lightweight ORM. The
knexfile.js serves as a global configuration for this thing, accessible both to the application code, as well as to the migration tool, and it depends on the contents of the
.env file being visible in the environment. There isn’t yet any application code, but migrations are more or less setup. The
./migrations directory holds them as
.js files, in the Knex fashion. The single migration looks like this:
up is executed to apply the change, whereas
down is executed in case
up errors and we need to revert the changes. The changes themselves are applied via
yarn migrate or
./scripts/migrate.sh. The database they apply to is configured, again, via
.env and the
docker-compose up postgres is enough to bring an instance up for local testing, whereas the Travis supplied one is used in testing.
Speaking of testing, it is now a lot more involved. There’s a bunch more tests under
./tests to boot. As stated in the previous post, we use a standard setup of mocha, chai and sinon. An example which highlights the usage of all these things, as well as testing a controller from NestJs (as an unit test even) is
./tests/app-controller-test.ts. I’ve reproduced a part of it here:
Furthermore, I’ve setup end to end tests, via Cypress.io. The test code lives under
./test-e2e, and it can be run via
yarn test:e2e or
./scripts/test-e2e.sh. It has a bit of a different setup because Cypress isn’t that well integrated with TypeScript (at least in my experience). So there’s a
tsconfig.e2e.json for it to use, and an explicit build step which takes source
.ts files and produces something the Cypress runner can work with in
The nice way to run these tests locally though is via
yarn test:e2e:watch, which launches the Cypress application - a sort of small-scale browser where you can have a lot of powerful interactions with the application.
Runs on Travis also send a capture of the test to Cypress, and you can view them here. A screenshot below:
Finally, a very useful tool for larger scale manual testing is ngrok. Use
yarn ngrok to launch the service and follow its instructions to temporarily publish the application online.
That’s it for this part. As promised, we dug more into the meat of the issue, though there’s still a lot of ground to cover. We’ll see about setting up sessions support, common middleware and Auth0+GitHub integrations in the next part.
Checkout all the articles in the series here.