Kā izveidot degošu ātru REST API ar Node.js, MongoDB, Fastify un Swagger

Iespējams, nevienam tīmekļa izstrādātājam nav svešs REST API un izaicinājumi, ko rada efektīva un efektīva API risinājuma izstrāde.

Šīs problēmas ietver:

  • Ātrums (API reakcijas laiks)
  • Dokumentācija (Notīrīt kodolīgus dokumentus, aprakstot API)
  • Arhitektūra un ilgtspēja (uzturējama un paplašināma koda bāze)

Šajā apmācībā mēs apskatīsim visu iepriekš minēto, izmantojot Node.js , MongoDB , Fastify un Swagger kombināciju .

Projekta pirmkods ir pieejams vietnē GitHub.

Pirms mēs sākam ...

Jums vajadzētu būt ar zināšanām iesācējiem / vidējā līmeņa JavaScript , esat dzirdējuši par Node.js un MongoDB un zināt, kas ir REST API .

Šeit ir dažas saites, lai jūs varētu atjaunināt:

  • JavaScript
  • Node.js
  • MongoDB
  • REST API

Tehnoloģija, kuru izmantosim:

  • Nostipriniet
  • Mangusts
  • Swagger

Ir ērti atvērt iepriekš minētās lapas jaunās cilnēs, lai tās būtu viegli atrast.

Jums būs jāinstalē šāda informācija:

  • MezglsJS / NPM
  • MongoDB
  • Pastnieks

Jums būs nepieciešams arī IDE un terminālis, es izmantoju iTerm2 Mac un Hyper Windows.

Sāksim!

Inicializējiet jaunu projektu, atverot termināli, izpildot katru no šīm koda rindām:

mkdir fastify-api cd fastify-api mkdir src cd src touch index.js npm init

Iepriekš minētajā kodā mēs izveidojām divus jaunus direktorijus, pārlūkojām tos, izveidojām index.jsfailu un parafējām savu projektu, izmantojot npm.

Inicializējot jaunu projektu, jums tiks piedāvāts ievadīt vairākas vērtības, kuras varat atstāt tukšas un vēlāk atjaunināt.

Pēc pabeigšanas srcdirektorijā tiek ģenerēts fails package.json . Šajā failā varat mainīt vērtības, kas ievadītas, kad projekts tika inicializēts.

Pēc tam mēs instalējam visas nepieciešamās atkarības :

npm i nodemon mongoose fastify fastify-swagger boom

Zemāk ir īss katras paketes darbības apraksts, kas ir citēts no viņu attiecīgajām vietnēm:

nodemons

nodemons ir rīks, kas palīdz izstrādāt uz node.js balstītas lietojumprogrammas, automātiski restartējot mezgla lietojumprogrammu, kad tiek konstatētas failu izmaiņas direktorijā.

nodemon neprasa nekādas papildu izmaiņas jūsu kodā vai izstrādes metodē. nodemon ir aizstājēja iesaiņotājs node, lai komandai izpildot nodemonvārdu aizstātu node, izpildot skriptu.

Lai iestatītu mezglu , mums package.jsonfailā skriptu objektā jāpievieno šāda koda rinda :

“start”: “./node_modules/nodemon/bin/nodemon.js ./src/index.js”,

package.jsonTagad mūsu failam vajadzētu izskatīties šādi:

{ "name": "fastify-api", "version": "1.0.0", "description": "A blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger.", "main": "index.js", "scripts": { "start": "./node_modules/nodemon/bin/nodemon.js ./src/index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Siegfried Grimbeek  (www.siegfriedgrimbeek.co.za)", "license": "ISC", "dependencies": { "boom": "^7.2.2", "fastify": "^1.13.0", "fastify-swagger": "^0.15.3", "mongoose": "^5.3.14", "nodemon": "^1.18.7" } }

mangusts

Mongoose nodrošina vienkāršu, uz shēmas balstītu risinājumu, lai modelētu lietojumprogrammas datus. Tas ietver iebūvētu tipa liešanu, validāciju, vaicājumu veidošanu, biznesa loģikas āķus un daudz ko citu.

nostiprināt

Fastify ir tīmekļa sistēma, kas īpaši vērsta uz vislabākās izstrādātāja pieredzes nodrošināšanu ar vismazākām izmaksām un jaudīgu spraudņu arhitektūru. To ir iedvesmojuši Hapi un Express, un, cik mēs zinām, tas ir viens no ātrākajiem tīmekļa ietvariem pilsētā.

ātri nostiprināt

Swagger dokumentācijas ģenerators programmai Fastify. Tas izmanto shēmas, kuras jūs deklarējat maršrutos, lai izveidotu pretrunīgu dokumentu.

bums

boom nodrošina virkni utilītu HTTP kļūdu atgriešanai.

Iestatiet serveri un izveidojiet pirmo maršrutu!

Pievienojiet index.jsfailam šādu kodu :

// Require the framework and instantiate it const fastify = require('fastify')({ logger: true }) // Declare a route fastify.get('/', async (request, reply) => { return { hello: 'world' } }) // Run the server! const start = async () => { try { await fastify.listen(3000) fastify.log.info(`server listening on ${fastify.server.address().port}`) } catch (err) { fastify.log.error(err) process.exit(1) } } start()

Mums ir nepieciešama Fastify ietvars, paziņojam par savu pirmo maršrutu un inicializējam serveri. port 3000Kods ir diezgan pašsaprotams, taču ņemiet vērā opciju objektu, kas nodots, inicializējot Fastify :

// Require the fastify framework and instantiate it const fastify = require('fastify')({ logger: true })

The above code enables Fastify’s built in logger which is disabled by default.

You can now run the follow code in your src directory in your terminal:

npm start

Now when you navigate to //localhost:3000/ you should see the {hello:world} object returned.

We will get back to the index.js file but for now let’s move on to setting up our database.

Start MongoDB and create the model!

Once MongoDB has been successfully installed, you can open a new terminal window and start up a MongoDB instance by running the following:

mongod

With MongoDB, we do not need to create a database. We can just specify a name in the setup and as soon as we store data, MongoDB will create this database for us.

Add the following to your index.js file:

... // Require external modules const mongoose = require('mongoose') // Connect to DB mongoose.connect(‘mongodb://localhost/mycargarage’) .then(() => console.log(‘MongoDB connected…’)) .catch(err => console.log(err)) ...

In the above code we require Mongoose and connect to our MongoDB database. The database is called mycargarage and if all went well, you will now see MongoDB connected... in your terminal.

Notice that you did not have to restart the app, thanks to the Nodemon package that we added earlier.

Now that our database is up and running, we can create our first Model. Create a new folder within the src directory called models, and within it create a new file called Car.js and add the following code:

// External Dependancies const mongoose = require('mongoose') const carSchema = new mongoose.Schema({ title: String, brand: String, price: String, age: Number, services: { type: Map, of: String } }) module.exports = mongoose.model('Car', carSchema)

The above code declares our carSchema that contains all the information related to our cars. Apart from the two obvious data types: String and Number. We also make use of a Map which is relatively new to Mongoose and you can read more about it here. We then export our carSchema to be used within our app.

We could proceed with setting up our routes, controllers and config in the index.js file, but part of this tutorial is demonstrating a sustainable codebase. Therefore each component will have its own folder.

Create the car controller

To get started with creating the controllers, we create a folder in the src directory called controllers, and within the folder, we create a carController.js file:

// External Dependancies const boom = require('boom') // Get Data Models const Car = require('../models/Car') // Get all cars exports.getCars = async (req, reply) => { try { const cars = await Car.find() return cars } catch (err) { throw boom.boomify(err) } } // Get single car by ID exports.getSingleCar = async (req, reply) => { try { const id = req.params.id const car = await Car.findById(id) return car } catch (err) { throw boom.boomify(err) } } // Add a new car exports.addCar = async (req, reply) => { try { const car = new Car(req.body) return car.save() } catch (err) { throw boom.boomify(err) } } // Update an existing car exports.updateCar = async (req, reply) => { try { const id = req.params.id const car = req.body const { ...updateData } = car const update = await Car.findByIdAndUpdate(id, updateData, { new: true }) return update } catch (err) { throw boom.boomify(err) } } // Delete a car exports.deleteCar = async (req, reply) => { try { const id = req.params.id const car = await Car.findByIdAndRemove(id) return car } catch (err) { throw boom.boomify(err) } }

The above may seem like a little much to take in, but it is actually really simple.

  • We require boom to handle our errors: boom.boomify(err).
  • We export each of our functions which we will use in our route.
  • Each function is an async function that can contain an await expression that pauses the execution of the async function and waits for the passed Promise’s resolution, and then resumes the async function’s execution and returns the resolved value. Learn more here.
  • Each function is wrapped in a try / catch statement. Learn more here.
  • Each function takes two parameters: req (the request) and reply (the reply). In our tutorial we only make use of the request parameter. We will use it to access the request body and the request parameters, allowing us to process the data. Learn more here.
  • Take note of the code on line 31:

    const car = new Car({ …req.body })

    This makes use of the JavaScript spread operator. Learn more here.

  • Take note of the code on line 42:

    const { …updateData } = car

    This makes use of the JavaScript destructuring in conjunction with the spread operator. Learn more here.

Other than that, we make use of some standard Mongoose features used to manipulate our database.

You are probably burning to fire up your API and do a sanity check, but before we do this, we just need to connect the controller to the routes and then lastly connect the routes to the app.

Create and import the routes

Once again, we can start by creating a folder in the root directory of our project, but this time, it is called routes. Within the folder, we create an index.js file with the following code:

// Import our Controllers const carController = require('../controllers/carController') const routes = [ { method: 'GET', url: '/api/cars', handler: carController.getCars }, { method: 'GET', url: '/api/cars/:id', handler: carController.getSingleCar }, { method: 'POST', url: '/api/cars', handler: carController.addCar, schema: documentation.addCarSchema }, { method: 'PUT', url: '/api/cars/:id', handler: carController.updateCar }, { method: 'DELETE', url: '/api/cars/:id', handler: carController.deleteCar } ] module.exports = routes

Here we are requiring our controller and assigning each of the functions that we created in our controller to our routes.

As you can see, each route consists out of a method, a url and a handler, instructing the app on which function to use when one of the routes is accessed.

The :id following some of the routes is a common way to pass parameters to the routes, and this will allow us to access the id as follows:

//127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323

Putting it all together and testing our API

Now that we have most of our parts constructed, we just need to connect them all together to start serving data via our API. Firstly we need to import our routes that we created by adding the following line of code to our main index.js file:

const routes = require(‘./routes’)

We then need to loop over our routes array to initialise them with Fastify. We can do this with the following code, which also needs to be added to the main index.js file:

routes.forEach((route, index) => { fastify.route(route) })

Now we are ready to start testing!

The best tool for the job is Postman, which we will use to test all of our routes. We will be sending our data as raw objects in the body of the request and as parameters.

Finding all cars:

Finding a single car:

Adding a new car**:

** The services appear to be empty, but the information does in fact persist to the database.

Updating a car:

Deleting a car:

We now have a fully functional API — but what about the documentation? This is where Swagger is really handy.

Adding Swagger and wrapping up.

Now we will create our final folder called config. Inside we will create a file called swagger.js with the following code:

exports.options = { routePrefix: '/documentation', exposeRoute: true, swagger: { info: { title: 'Fastify API', description: 'Building a blazing fast REST API with Node.js, MongoDB, Fastify and Swagger', version: '1.0.0' }, externalDocs: { url: '//swagger.io', description: 'Find more info here' }, host: 'localhost', schemes: ['http'], consumes: ['application/json'], produces: ['application/json'] } }

The above code is an object with the options which we will pass into our fastify-swagger plugin. To do this, we need to add the following to our index.js file:

// Import Swagger Options const swagger = require(‘./config/swagger’) // Register Swagger fastify.register(require(‘fastify-swagger’), swagger.options)

And then we need to add the following line after we have initialised our Fastify server:

... await fastify.listen(3000) fastify.swagger() fastify.log.info(`listening on ${fastify.server.address().port}`) ...

And that is it! If you now navigate to //localhost:3000/documentation, you should see the following:

As simple as that! You now have self updating API documentation that will evolve with your API. You can easily add additional information to your routes, see more here.

Whats Next?

Now that we have a basic API in place, the possibilities are limitless. It can be used as the base for any app imaginable.

Nākamajā apmācībā mēs integrēsim GraphQL un galu galā integrēsim arī priekšgalu ar Vue.js !