Left-facing arrow
back to blog
SOFTWARE DEVELOPMENT

The Modern API, Part 2

In The Modern API, Part 1, we went over how to set up your development environment for a Node API. Here in Part 2, we'll start writing some code. We will write some "API startup" logic, create a root route (<code>GET /<code>) which displays information about the API, and set up the test suite to start testing endpoints and checking responses.

TL;DR: https://github.com/decentorganization/decent-api

API Startup

Our "start" scripts in <code>package.json<code> execute <code>src/index.js<code>, so let's start there.

src/index.js

Imports the <code>http<code> library so we can listen and respond to requests.

Imports <code>dotenv<code> and <code>config<code>ures it so that we can pull in local environment variables when running the API.

Imports <code>debug<code> and creates an instance of it which is a nice library to assist with debug logging.

Imports two functions called <code>api<code> and <code>databaseSetup<code> which we've created elsewhere in the repo, which get us instances of a database and an Express API (shocking).

Defines a function called <code>normalizePort<code> which tries its hardest to return an integer based on its input.

Defines two functions called <code>onError<code> and <code>onListening<code> which are "callbacks" for the server created by <code>http.createServer()<code>, which get called when the server can't start up successfully, or does start up successfully and is now listening for requests.

Calls the <code>databaseSetup()<code> function, passing in the "name" that we want to give to our database. It returns a promise which resolves to an instance of a database object. We pass that database object into our <code>api()<code> function, which returns an instance of an Express API. Finally, we create that <code>http<code> server, listen on a port, and we've got a running API!

Let's see what that <code>databaseSetup<code> function does.

src/database/index.js

Imports <code>debug<code> and creates an instance of it for nice debug logs.

Imports <code>path<code> so we can safely build the path to our "migrations" directory.

Imports <code>knex-db-manager<code>, which is a nice library we use to manage the "database server", instead of just a single database. We can use this library to create new databases. The standard <code>knex<code> library operates on the concept of a database which already exists.

Imports a function called <code>setDatabase<code> which we'll use to build up a set of database functions to manipulate CRUD data. We won't get too deep in here in this guide, look out for Part 3 when we model out some business logic.

The single exported function called <code>databaseSetup<code> in here does a lot.

First, it creates a <code>config<code> object which is passed into the <code>knex-db-manager<code>'s <code>databaseManagerFactory<code> function, to give us an instance of our database manager object, which I've called <code>dbManager<code>.

As we learned from <code>src/index.js<code>, this function returns a Promise. Let's step through it.

First, use the <code>dbManager<code> to <code>createDbOwnerIfNotExist<code>, to make sure we have proper permissions to create new databases.

Next, get an instance of the database itself with <code>knexInstance<code>, (which is based on the database name passed into this function, and set in the <code>config<code> object).

If this is the first time that the API has been started, and we never previously created any database, then what's going to happen? We need to check for this case, and then create a database if necessary. The best way I've found to do this is to just send a raw query to the database, and see if it errors out or not.

So, <code>'select 1+1 as result'<code> and if there's no error, great, we continue with the promise chain. If there is an error, we catch it, then use the <code>dbManager<code> to <code>createDb<code>.

Next, we perform migrations, and then the last step in our promise chain is to build a little object that includes the <code>manager<code> (which is used in the test suite), and the <code>database<code> (which itself is an object that implements a bunch of specific CRUD operations that the API uses, which we'll get into in the next guide).

src/api.js

Nice and simple in here.

Import <code>express<code>. Import a few <code>middleware<code> functions. Import a <code>router<code>. Technically everything in an express app is "middleware". Anyway.

We export this function called <code>api<code>, which takes a database object as input. First, it creates an <code>express<code> server instance. Then, it passes that <code>server<code> instance into all of those middleware functions, one by one. It also passes the <code>database<code> object into our <code>storage<code> middleware function. Finally, return that <code>express<code> server.

src/middleware/index.js

Imports <code>debug<code> and creates an instance of it for nice debug logs, which we'll use in our error handler.

Imports <code>express<code>, which we'll use to pull the <code>json<code> middleware function off of.

Imports <code>morgan<code>, which is a nice library for logging HTTP requests.

Imports <code>cors<code>, a library that implements middleware which enables CORS support for the API.

The <code>cors<code> and <code>encoding<code> functions are simple enough. They take the express api as input, and apply the CORS and JSON middleware. This lets our API support CORS, and properly respond to JSON requests (with JSON responses).

The <code>logging<code> function sets up our <code>morgan<code> import. Basically, we don't care about logging HTTP requests when running tests. When developing locally, print some nice simple color-coded logs, and on any other environment print out more detailed logs.

The <code>storage<code> function takes the <code>api<code> and a <code>db<code> as input, and registers a middleware function which sets a "local" value that we define on the spot, called <code>db<code>, setting it to the <code>db<code> which was passed into the function. Then, it calls <code>next()<code> to continue with the middleware chain. This is crucial to the operational integrity of our API, as we now have a database which lives within each request. We don't need some global database object throughout our codebase, for every request lifecycle we can pull it right out of the request. This allows us to set a specific database (or database mock object...) when running the app in dev, staging, production, or tests.

The <code>errors<code> function registers a middleware with 4 inputs. This is special syntax in Express, as it signifies that this is an "error" handling middleware. Any error thrown in the app will get caught by this middleware. We've got some logic here that crafts up specific error messages based on the type of error (whether it was expected and caught by the app logic, or some other unknown error), and which environment we're running in. Finally, it prints the error out onto the console via <code>debug<code>.

src/router/index.js

The final piece of our API startup logic. The exported function <code>router<code> just registers a couple of routes called <code>/<code> and <code>/notes<code>. In this guide we'll see how the root <code>/<code> route is built. We'll cover our notes CRUD next time.

The <code>root<code> handler function for our <code>/<code> route takes in the express API instance as a parameter. Let's go check that out.

Root Route

Finally, our app is set up and ready to handle requests. Let's see how the root <code>GET /<code> route is configured and handled.

src/router/root.js

Imports the <code>Router<code> function from <code>express<code>, so that we can create a new route handler.

Imports a function called <code>getRoot<code> from our root controller.

Our exported <code>root<code> function takes the express API as an input. It creates a new <code>Router<code> instance, then registers a <code>.get<code> method for the <code>/<code> route, calling <code>getRoot(api)<code> as the handler.

src/controllers/root.js

Imports <code>child_process<code> which allows us to execute commands on the underlying system.

Imports <code>express-list-endpoints<code>, a package that takes an express API and outputs a object containing all of the registered routes.

Imports the <code>project's package.json<code> file, so we can pull information out of it, namely the <code>version<code> and <code>name<code>.

Builds up a <code>version<code> variable of the form (<code>version<code> from <code>package.json<code> , then a <code>+<code>, then the short git hash from the commit that is currently checked out).

The exported <code>getRoot<code> function here returns a handler which sends a <code>200<code> response object containing some information about the API.

The JSON response keys include:

  • '๐Ÿ‘‹', with a value of '๐ŸŒŽ' (because emojis are fun)
  • <code>'name'<code>, with a value of the name of the project from <code>package.json<code>
  • <code>'environment'<code>, with a value of the <code>NODE_ENV<code> environment variable
  • <code>'version'<code>, with a value of that <code>version<code> variable that we built above
  • <code>'endpoints'<code>, with a value of the object that <code>express-list-endpoints<code> spits out

For example, it looks something like this:

-- CODE language-json -- { "๐Ÿ‘‹": "๐ŸŒŽ", "name": "decent-api", "environment": "development", "version": "0.0.0+d314f03", "endpoints": [ { "path": "/", "methods": [ "GET" ] } ] }

Cool! So if this API is running on your local machine using the default port, then if you hit <code>http://localhost:3000<code> in your browser, you'll see the above output (Firefox will even format the JSON nicely for you).

Let's go write some tests.

Testing

No good software project is complete without a test suite. We'll cover how to set up the test suite, then write some integration tests for our root endpoint.

When we call <code>mocha<code> through our <code>test<code> script (<code>yarn test<code>), it just executes any files that end in <code>.spec.js<code> that live in the root of our <code>test<code> directory. So, we'll create an <code>index.spec.js<code> there which will trigger the rest of our tests.

When running tests, be sure your local database server is running via <code>docker-compose up<code>.

test/index.spec.js

This is simple. Just import our <code>integration<code> tests (via a function called integration), then call it.

test/integration/index.spec.js

Imports <code>dotenv<code> and <code>config<code>ure it in case we're not using environment variable defaults.

Imports two functions called <code>api<code> and <code>databaseSetup<code> which get us instances of a database and an Express API (shocking). Look familiar? We import these into our base <code>src/index.js<code> as well, for bootstrapping the API.

Imports a <code>root<code> function which is our root integration tests. Also imports a <code>notes<code> integration test function but we'll cover that in the next guide.

In here is where we set up some architecture for the integration tests. The exported <code>integration<code> function wraps everything in a <code>describe<code> block called "integration". Next, we define a couple of "private" variables called <code>_testDbManager<code> and <code>_testApi<code>, which will hold instances of the database server manager, and an express API.

The <code>before<code> block, which is called before any tests in the current <code>describe<code> block are executed, calls <code>databaseSetup<code> with the name "test" (from <code>NODE_ENV<code> environment variable). This will create a new database called "test", then return the database manager details to the resolved promise. We'll take the <code>manager<code> and save it into <code>_testDbManager<code>, then pass the <code>database<code> into <code>api()<code> to create a new instance of our Express API, and save that along into <code>_testApi<code>.

We've also created an <code>after<code> block (which is executed after all tests in the <code>describe<code> block are finished running). In here, we use our database manager to drop the "test" database, then close our connection to the local server.

It's important to realize here that we are not starting an HTTP server at all, like we do in <code>src/index.js<code>. We just have an instance of an Express API, which we manually pass requests into (see the next section).

Smacked between our <code>before<code> and <code>after<code> blocks we call our imported <code>root<code> function, which as an inputs takes a function which returns our <code>_testApi<code> instance, and a "route" for this test (<code>/<code>).

Let's see what this <code>root<code> function does.

test/integration/root.js

Imports <code>chai<code> and <code>chai-http<code>, then configures them, so that we can use chai for testing our API requests.

The exported <code>root<code> function takes as inputs a function called <code>api<code>, and the <code>route<code>. It's wrapped in a <code>describe<code> block named <code>${route} route tests<code>, which inside of it has another <code>describe<code> block named <code>GET ${route}<code>. Since our root route only supports the <code>GET<code> method, we don't have any other <code>describe<code> blocks in here. If we allowed users to <code>POST<code> to <code>/<code>, then we'd have another <code>describe<code> block as a sibling of our <code>GET ${route}<code> block called <code>POST ${route}<code>.

The tests themselves are simple. First, we create a variable called <code>response<code>, then we have a <code>before<code> block which is called before any tests are ran. This <code>before<code> block uses <code>chai.request<code> and the instance of our API (which we get by calling the <code>api<code> function that was passed in), to call <code>GET /<code> (via <code>.get(route)<code>), and then saves the response into the <code>response<code> variable.

Next, we define some tests using <code>it<code> blocks to check various things on that response object. It should have a <code>200<code> status code. It should be an object. It should have the "hello world" emojis. It should return the project name as a string. It should return the environment. It should return the version. It should return an array of routes.

-- CODE language-shell -- integration / route tests GET / โœ“ should have 200 status โœ“ should return an object โœ“ should return hello world emojis โœ“ should return project name โœ“ should return environment โœ“ should return version โœ“ should have a list of endpoints 7 passing (743ms)

Conclusion

In this post we learned how to bootstrap an Express API using a modular, reusable architecture that decouples the database, the Express API, and the HTTP server.

We created a root route handler, and made it return various configuration information about the API itself.

Finally, we built a test suite that we'll be able to easily add onto as we create more business logic, models, routes, and controllers.

Join me in the next post where we'll go over how to build some CRUD around the concept of "notes". Or just go look at the code, yourself, right now.

Questions? Comments? Ask away over at the Medium post

Adam Gall

Build with Us

We are always interested in connecting with people who want to fund, innovate
or work in the decentralized economy.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.