Używanie Node.js, Express i Express Router do implementacji bardzo prostego interfejsu API REST

Aug 23 2020

Aby poznać możliwości Node.js i Express, próbuję zaimplementować bardzo prosty interfejs API REST. API mówi, co powinno zrobić, zamiast to robić, więc mogę skupić się na strukturze frameworka. Wymagania dotyczące interfejsu API są następujące:

  • Mock API do gry z pytaniami i odpowiedziami.
  • / qanda endpoint: repozytorium wszystkich par pytanie-odpowiedź.
  • / qanda / questionId endpoint: konkretna para pytanie-odpowiedź.
  • Powinien obsługiwać czasowniki HTTP GET, POST, PUT, DELETE.
  • Załóżmy, że treść żądania HTTP to JSON.

Wszystko to jest arbitralne. Po prostu próbuję się zawiesić w obsłudze i analizowaniu żądań i odpowiedzi HTTP. Aby utrzymać porządek, używam również modułów Express Router i Node.js.

Oto index.js

'use strict';
const express = require('express');

const qanda = require('./routes/qanda.js');

const port = 9999;
const app = express();

app.use('/qanda', qanda);

app
  .all('/', (req, res, nxt) => {
  res
    .status(200)
    .send('Welcome!');
  })
  .listen(port, () => {
    console.log(`Listening to localhost:${port}`);
  });

A oto ./routes/qanda.js

'use strict';
const express = require('express');

const qandaRouter = express.Router();
qandaRouter.use(express.json());

qandaRouter
  .route('/')
  .get((req, res, nxt) => {
    res
      .status(200)
      .send('Sending all the qandas!');
  })
  .post((req, res, nxt) => {
    res
      .status(200)
      .send(`Adding ${req.body.q} = ${req.body.a}`); }) .put((req, res, nxt) => { res .status(405) .send('PUT not supported.'); }) .delete((req, res, nxt) => { res .status(200) .send('Deleting all qands!'); }); qandaRouter .route('/:qId') .get((req, res, nxt) => { res .status(200) .send(`Sending qanda ${req.params.qId}`);
  })
  .post((req, res, nxt) => {
    res
      .status(405)
      .send('POST not supported.');
  })
  .put((req, res, nxt) => {
    res
      .status(200)
      .send(`Updating ${req.params.qId} with ${req.body.q} = ${req.body.a}`); }) .delete((req, res, nxt) => { res .status(200) .send(`Deleting qanda ${req.params.qId}`);
  });

module.exports = qandaRouter;

Nie mogę się doczekać wszelkiego rodzaju opinii! Od stylu po najlepsze praktyki, anty-wzorce - cokolwiek przyjdzie Ci do głowy, jest bardzo cenione! Mam kilka konkretnych pytań:

  • Czy metoda łańcuchowa jest zwykłym sposobem obsługi czegoś takiego w Expressie?
  • Czy styl łączenia metod jest czytelny i możliwy do utrzymania?
  • Czy istnieje bardziej zwięzły sposób na osiągnięcie tego?
  • Co zrobiłbyś inaczej?

Odpowiedzi

6 KaterAkeren Aug 23 2020 at 20:44

Po przeanalizowaniu napisanych przez ciebie kodów mogłem dostrzec pewne rzeczy, które nie są właściwie zrobione. Poniżej znajduje się kilka rzeczy, które podkreśliłem:

  • Struktura bazy kodu nie jest w porządku, dlatego może być możliwa do utrzymania i skalowalna, jeśli baza kodu się rozrośnie.
  • Konwencja nazewnictwa RESTful API nie jest akceptowana. Punkty końcowe dowolnego interfejsu API REST powinny zawierać tylko zasoby (rzeczowniki) i używać metod (czasowników) HTTP do wykonywania czynności. Nazwałeś swój punkt końcowy jako / qanda zamiast / qandas . Zalecam przeczytanie tych wytycznych RESTful API Designing autorstwa Mahesha Haldara opublikowanych na Hackernoon. To świetne źródło informacji dla programistów.
  • Po trzecie, nie oddzieliłeś swojego serwera od express; oba są połączone razem. Nie jest to najlepsza praktyka.
  • Kiedy API jest używane przez inne aplikacje, uaktualnienie API z pewnymi zmianami również doprowadziłoby do zerwania istniejącego kontraktu usługowego - zostało to zauważone przez wiele aplikacji, które świadczą usługi API. Najlepszą praktyką jest zawsze poprzedzać wszystkie adresy URL przedrostkiem / api / v1 / users . Jeśli istnieją jakiekolwiek istotne uaktualnienie łamanie możemy nazwać nowy zestaw API jak V2, V3 lub v1.XX . odpowiednio.

Z własnego doświadczenia podszedłbym do tego w następujący sposób:

  • Najpierw utworzę katalog o nazwie qanda-api , w nim utworzę inne katalogi o nazwie src oraz dwa pliki app.js i server.js, które będą znajdować się w katalogu głównym z src
  • Po drugie, w src utworzę dwa podkatalogi zwane trasami i kontrolerami
  • Po trzecie, zainicjuję rejestr npm w katalogu głównym katalogu projektu, który śledzi wszystkie zainstalowane pakiety npm , wpisując poniższe polecenie za pośrednictwem terminala:

npm init

  • Następnie zainstaluję trzy zależności, których będę używać przez ten sam terminal, ale tym razem musisz mieć dostęp do internetu:

npm zainstaluj express dotenv morgan

  • Jeśli powyższy proces zostanie wykonany pomyślnie, zobaczysz zainstalowane pakiety w pliku o nazwie package.json

Teraz zróbmy kod, brudząc sobie ręce

  • Najpierw utwórzmy plik o nazwie controllers / qandaController.js w innym, aby zapisać naszą logikę qanda, która zostanie wyeksportowana do pliku trasy.
exports.getAllQandas = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Documments retrieved successfully',
        data: 'The data goes here from the data!'
    });
};

exports.getQanda = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Doc retrieved successfully',
        data: 'The data goes here from the data!'
    });
};

exports.createQanda = (req, res) => {
    res.status(201).json({
        status: 'success',
        message: 'Qanda created successfully',
        data: 'Return the created data here!'
    });
};

exports.updateQanda = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Doc updated successfully',
        data: 'The updated data goes here!'
    });
};

exports.deleteQanda = (req, res) => {
    res.status(200).json({
        status: 'success',
        message: 'Doc deleted!',
        data: 'Return the Deleted data'
    });
};
  • Teraz nadszedł czas, abyśmy stworzyli naszą trasę qanda w plikach Routes / qandaRoutes.js . Następnie zaimportujemy logikę kontrolera do jego obsługi trasy poniżej.
const express = require('express');
/**
 * import the qanda controller
 */
const qandaController = require('./../controllers/qandaController');

const router = express.Router();

router
    .route('/')
    .get(qandaController.getAllQandas)
    .post(qandaController.createQanda);

router
    .route('/:id')
    .get(qandaController.getQanda)
    .patch(qandaController.updateQanda)
    .delete(qandaController.deleteQanda);

module.exports = router;
  • Otwórzmy plik, który utworzyliśmy wcześniej, o nazwie app.js w innym, aby skorzystać z zainstalowanego ekspresu. Zawartość tego pliku jest przeznaczona tylko do ekspresu, który jest następnie eksportowany na jego serwer:
const express = require('express');
const morgan = require('morgan');

const qandaRouter = require('./routes/qandaRoutes');

const app = express();
    
if (process.env.NODE_ENV === 'development') app.use(morgan('dev'));

// ROUTES
app.use('/api/v1/qandas', qandaRouter);
    
/*
 ** HANDLING UNHANDLED ROUTES
 */
app.all('*', (req, res, next) => {
    res.status(404).json({
        status: 'fail',
            message: `Can't find ${req.originalUrl} on this Server!`
    });
});
   
module.exports = app;
  • Stwórzmy nasz serwer, który stanie się naszym punktem wejścia do aplikacji. W tym miejscu następuje faktyczne wykonanie, w którym wyeksportowany plik app.js jest używany przez wstawienie go jako argumentu w funkcji createServer ().
require('dotenv').config({ path: '.env' });
const http = require('http');

const app = require('./app');

const port = process.env.PORT || 5000;

const server = http.createServer(app);

server.listen(port, () => console.log(`App running on port ${port}`));

Wniosek

Zdecydowanie zaleca się, aby podczas pisania kodów zawsze rozdzielać obawy i mieć czystą bazę kodu, którą można utrzymać i skalować w czasie. Jeśli chodzi o projektowanie API, organizacja kodu to coś więcej, a jedną z nich jest zasada DRY (Don't Repeat Yourself). Jeśli chcesz dowiedzieć się więcej o projektowaniu API w Node.JS, polecam ten kurs Jonasa . Demistyfikuje abstrakcyjne koncepcje i uczył najlepszych praktyk. Zawarłem tę odpowiedź w projektach API w Github: API i Tourism API . Możesz klonować lub modyfikować według potrzeb.