Intro

Welcome back to our project on building and deploying a Node.js API to the cloud via Digital Ocean. Yesterday, we went through a basic introduction to what our project is and how it will work. We also configured MLab and got ourselves setup and familiarized with Digital Ocean. So today, we will now shift our focus to actually building our API. Let's get started.

Building the API

Before we begin, we need to go over a few project requirements. We will need...

  1. Node version 8.x.x or higher
  2. nodemon
  3. Access to MLab (if you haven't done the set up from yesterday then go ahead and do that).
  4. yarn

Once you know you have all of those things in place, open your web browser and login to Github to create a new repository called todo-api.

(video showing the creation of the new repository then copying the git clone url)

Excellent, now let's open our terminal and handle some basic setup.

cd /your/project/directory
git clone /your/github/url
cd todo-api
yarn init

Good, now let's open package.json in our text editor and fill in some changes.

{
  "name": "koa-demo",
  "version": "0.0.1",
  "description": "A Demo Rest API written in Koa.js.",
  "main": "app.js",
  "author": "Your name here",
  "license": "MIT",
  "scripts": {
    "start": "nodemon app.js"
  },
  "dependencies": {
    "bluebird": "^3.5.0",
    "koa": "^2.2.0",
    "koa-bodyparser": "^4.2.0",
    "koa-convert": "^1.2.0",
    "koa-handle-error": "^0.0.5",
    "koa-json-error": "^3.1.2",
    "koa-logger": "^3.0.0",
    "koa-res": "^1.3.0",
    "koa-simple-router": "^0.2.0",
    "mongoose": "^4.10.3"
  }
}

Great, now let's run back to our terminal and handle some more tasks.

yarn install
touch app.js
mkdir model controller
touch model/Task.js
touch controller/task.js

Now let's open the project in our text editor and start writing out our app.js file.

/*
    Include modules
*/
const koa = require('koa')
const mongoose = require('mongoose')
const convert = require('koa-convert')
const bodyParser = require('koa-bodyparser')
const router = require('koa-simple-router')
const error = require('koa-json-error')
const logger = require('koa-logger')
const koaRes = require('koa-res')
const handleError = require('koa-handle-error')
const task = require('./controller/task')
const app = new koa()

/*
    Mongoose Config
*/
mongoose.Promise = require('bluebird')
mongoose
.connect(process.env.MONGO_URL)
.then((response) => {
    console.log('mongo connection created')
})
.catch((err) => {
    console.log("Error connecting to Mongo")
    console.log(err);
})

/*
    Server Config
*/
// error handling
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    ctx.status = err.status || 500
    ctx.body = err.message
    ctx.app.emit('error', err, ctx)
  }
})

// logging
app.use(logger())
// body parsing
app.use(bodyParser())
// format response as JSON
app.use(convert(koaRes()))
// configure router
app.use(router(_ => {
    _.get('/saysomething', async (ctx) => {
        ctx.body = 'hello world'
    }),
    _.get('/throwerror', async (ctx) => {
        throw new Error('Aghh! An error!')
    }),
    _.get('/tasks', task.getTasks),
    _.post('/task', task.createTask),
    _.put('/task', task.updateTask),
    _.delete('/task', task.deleteTask),
    _.post('/task/multi', task.createConcurrentTasks),
    _.delete('/task/multi', task.deleteConcurrentTasks)
}))

app.listen(3000)

Excellent, as you can see this is made to be ultra simple. We have imported our modules, connected to our mongo database, configured our error handler, configured some other middleware, then setup our routes. It's important to note the process.env.MONGO_URL shown here. This will access the MONGO_URL environment variable we will later set in FreeBSD to connect to our database on MLab. That being said, if you move to test the app now, it may be easier to just paste in your MLab url for now then switch it back when you are done testing.

Now let's open model/Task.js and fill out the code for this.

const mongoose = require('mongoose')
/*
    Task Schema
*/
const TaskSchema = mongoose.Schema = {
    name: String,
    urgency: String
}

module.exports = mongoose.model("Task", TaskSchema)

There you go, this has to be the world's most simple data model in history but it will suit our present needs. We have a task, it has a name, and it has a level urgency. Easy. Let's move forward and write out controller/task.js

const Task = require('../model/Task')

exports.getTasks = async (ctx) => {
    const tasks = await Task.find({})
    if (!tasks) {
        throw new Error("There was an error retrieving your tasks.")
    } else {
        ctx.body = tasks
    }
}

exports.createTask = async (ctx) => {
    const {name, urgency} = ctx.request.body
    const result = await Task.create({
        name: name,
        urgency: urgency
    })
    if (!result) {
        throw new Error('Task failed to create.')
    } else {
        ctx.body = {message: 'Task created!', data: result}
    }
}

exports.updateTask = async (ctx) => {
    const {name, newName, newUrgency} = ctx.request.body
    const searchByName = {name: name}
    const update = {name: newName, urgency: newUrgency}
    const result = await Task.findOneAndUpdate(searchByName, update)
    if (!result) {
        throw new Error('Task failed to update.')
    } else {
        console.log(result)
        ctx.body = {message: 'Task updated!', data: result}
    }
}

exports.deleteTask = async (ctx) => {
    const {name} = ctx.request.body
    const result = await Task.findOneAndRemove({name})
    if (!result) {
        throw new Error('Task failed to delete.')
    } else {
        ctx.status = 200
        ctx.body = {message: 'success!'}
    }
}

exports.createConcurrentTasks = async (ctx) => {
    const {nameTaskOne, urgencyTaskOne, nameTaskTwo, urgencyTaskTwo} = ctx.request.body
    const taskOne = Task.create({
        name: nameTaskOne,
        urgency: urgencyTaskOne
    })
    const taskTwo = Task.create({
        name: nameTaskTwo,
        urgency: urgencyTaskTwo
    })
    const [t1, t2] = await Promise.all([taskOne, taskTwo])
    if (!t1 || !t2) {
        throw new Error('Tasks failed to be created.')
    } else {
        ctx.body = {message: 'Tasks created!', taskOne: t1, taskTwo: t2}
    }
}

exports.deleteConcurrentTasks = async (ctx) => {
    const {nameTaskOne, nameTaskTwo} = ctx.request.body
    const taskOne = Task.findOneAndRemove({name: nameTaskOne})
    const taskTwo = Task.findOneAndRemove({name: nameTaskTwo})
    const [t1, t2] = await Promise.all([taskOne, taskTwo])
    if (!t1 || !t2) {
        throw new Error('Tasks failed to delete.')
    } else {
        ctx.body = {message: 'Tasks deleted successfully!'}
    }
}

Excellent, we have defined our CRUD operations that will be used in our routes we defined earlier in app.js.

Now take some time to ensure your project runs. Once you do, open your terminal and let's commit this to git and Github.

git commit -m "initial commit"
git push origin master

Conclusion

Great job, we have now written our API code and deployed it to Github. Tomorrow, we will shift our focus to configuring our FreeBSD instances on Digital Ocean. See you then.

Alex Allen

Alex is an independent developer who is obsessed with both performance and information security. When not writing code, Alex is either playing guitar or working in his garden.

  1. Comments for Build a Node.js API

You must login to comment

You May Also Like