Jak stworzyć bezpieczny interfejs API Node.js GraphQL

Opublikowany: 2022-03-11

W tym artykule postaramy się przedstawić krótki przewodnik po tym, jak stworzyć bezpieczne API Node.js GraphQL.

Niektóre pytania, które mogą przyjść do głowy, to:

  • Jaki jest cel korzystania z GraphQL API?
  • Co to jest API GraphQL?
  • Co to jest zapytanie GraphQL?
  • Jakie są zalety GraphQL?
  • Czy GraphQL jest lepszy niż REST?
  • Dlaczego używamy Node.js?

Wszystkie te pytania są słuszne, ale zanim na nie odpowiemy, powinniśmy zagłębić się w krótki przegląd obecnego stanu tworzenia stron internetowych:

  • Prawie każde rozwiązanie, które dziś znajdziesz, korzysta z pewnego rodzaju interfejsu programowania aplikacji (API).
  • Nawet jeśli korzystasz tylko z sieci społecznościowej, takiej jak Facebook lub Instagram, nadal masz połączenie z front-endem, który wykorzystuje interfejs API.
  • Jeśli jesteś ciekawy, przekonasz się, że prawie wszystkie internetowe usługi rozrywkowe korzystają z innego rodzaju interfejsu API, w tym usług takich jak Netflix, Spotify i YouTube.

Praktycznie w każdym scenariuszu znajdziesz API, którego nie musisz znać szczegółowo, np. nie musisz wiedzieć, jak zostały zbudowane i nie musisz używać tej samej technologii, co kiedyś w stanie zintegrować go z własnym systemem. Udostępniany interfejs API umożliwia oferowanie sposobu komunikowania się między usługami we wspólnym standardzie, w którym zarówno usługa, jak i klient mogą komunikować się bez konieczności polegania na określonym stosie technologicznym.

Dzięki dobrze zorganizowanemu interfejsowi API możliwe jest posiadanie solidnego, łatwego w utrzymaniu i skalowalnego interfejsu API, który może obsługiwać wiele rodzajów klientów i aplikacji typu front-end.

To powiedziawszy, co to jest GraphQL API?

GraphQL to język zapytań dla interfejsów API, opracowany do użytku wewnętrznego na Facebooku i opublikowany do użytku publicznego w 2015 roku. Obsługuje czytanie, pisanie i aktualizacje w czasie rzeczywistym. Jest to również open source i jest powszechnie porównywany do REST i innych architektur. Opiera się ona w dużym skrócie na:

  • Zapytania GraphQL — umożliwia to klientowi odczytywanie i manipulowanie sposobem odbierania danych.
  • Mutacje GraphQL - Oto jak zapisywać dane na serwerze. Jest to konwencja GraphQL dotycząca sposobu zapisywania danych w systemie.

Chociaż ten artykuł ma zademonstrować prosty, ale rzeczywisty scenariusz dotyczący budowania i używania interfejsów API GraphQL, nie będziemy dostarczać szczegółowego wprowadzenia do GraphQL. Powód jest prosty, ponieważ zespół GraphQL dostarcza obszerną dokumentację i wymienia kilka najlepszych praktyk we Wprowadzeniu do GraphQL.

Co to jest zapytanie GraphQL?

Jak opisano wcześniej, zapytanie jest sposobem, w jaki klient może odczytywać i manipulować danymi z interfejsu API. Możesz podać typ obiektu i wybrać rodzaj pól, które chcesz otrzymać z powrotem. Proste zapytanie wyglądałoby tak:

 query{ users{ firstName, lastName } }

W tym zapytaniu próbujemy dotrzeć do wszystkich użytkowników ze schematu naszych użytkowników, ale otrzymujemy tylko firstName i lastName . Wynik tego zapytania byłby na przykład:

 { "data": { "users": [ { "firstName": "Marcos", "lastName": "Silva" }, { "firstName": "Paulo", "lastName": "Silva" } ] } }

Jest to dość proste w obsłudze klienta.

Jaki jest cel korzystania z API GraphQL?

Celem tworzenia API jest możliwość posiadania oprogramowania jako usługi, którą można zintegrować z innymi usługami zewnętrznymi. Nawet jeśli Twoja aplikacja jest używana przez jeden front-end, możesz uznać ten fronton za usługę zewnętrzną, a do tego będziesz mógł pracować w różnych projektach, gdy komunikacja między nimi jest zapewniona za pośrednictwem interfejsu API.

Jeśli pracujesz w dużym zespole, można go podzielić, aby stworzyć zespół front-end i back-end, co pozwoli im korzystać z tej samej technologii i ułatwić im pracę. Podczas projektowania API ważne jest, aby wybrać lepsze dopasowanie do projektu i to, co przybliża Cię do pożądanego rozwiązania.

W tym artykule skupimy się na szkielecie do budowy API wykorzystującego GraphQL.

Czy GraphQL jest lepszy niż REST?

Może to trochę ucieczka, ale nic na to nie poradzę: to zależy

GraphQL to podejście, które bardzo dobrze pasuje do kilku scenariuszy. REST to podejście do architektury, które sprawdza się również w kilku scenariuszach. Obecnie istnieje mnóstwo artykułów, które wyjaśniają, dlaczego jeden jest lepszy od drugiego lub dlaczego powinieneś używać tylko REST zamiast GraphQL. A także wiele sposobów, w jakie można używać GraphQL wewnętrznie i nadal utrzymywać punkty końcowe API jako architekturę opartą na REST.

Najlepszą wskazówką byłoby poznanie korzyści z każdego podejścia, przeanalizowanie tworzonego rozwiązania, ocena komfortu pracy zespołu z tym rozwiązaniem oraz ocena, czy będziesz w stanie poprowadzić swój zespół w nauce i dostaniu się do przyspieszyć przed wyborem między podejściami.

Ten artykuł jest raczej praktycznym przewodnikiem niż subiektywnym porównaniem GraphQL i REST. Jeśli chcesz przeczytać szczegółowe porównanie tych dwóch, proponuję zapoznać się z innym z naszych artykułów, GraphQL vs. REST – samouczek GraphQL.

W dzisiejszym artykule skupimy się na tworzeniu GraphQL API przy użyciu Node.js.

Dlaczego używamy Node.js?

GraphQL ma kilka różnych bibliotek, których możesz użyć. Na potrzeby tego artykułu zdecydowaliśmy się na użycie JavaScript z Node.js ze względu na ich szerokie zastosowanie oraz fakt, że Node.js pozwala programistom używać znanej składni front-endu do programowania po stronie serwera.

Przydatne jest również porównanie z naszym podejściem z API opartym na REST, podobnym do tego, które zostało zaprezentowane w innym artykule Toptal Engineering Blog: Tworzenie bezpiecznego API REST w Node.js. W tym artykule przedstawiono również wykorzystanie Node.js z Express do opracowania szkieletowego interfejsu API REST, który umożliwi porównanie niektórych różnic między tymi dwoma podejściami. Node.js został również zaprojektowany ze skalowalnymi aplikacjami sieciowymi, globalną społecznością i kilkoma bibliotekami open source, które można znaleźć na stronie npm.

Tym razem pokażemy, jak zbudować szkielet API za pomocą GraphQL, Node.js i Express!

Ręce na samouczku GraphQL

Jak wspomniano wcześniej, będziemy budować szkielet pomysłu dla GraphQL API, a przed kontynuowaniem będziesz musiał znać podstawy Node.js i Express. Kod źródłowy projektu wykonanego dla tego przykładu GraphQL jest dostępny tutaj.

Zajmiemy się dwoma rodzajami zasobów:

  • Użytkownicy, dla których obsłużymy podstawowy CRUD.
  • Produkty, dla których będziemy mieli trochę szczegółów, aby pokazać więcej mocy GraphQL.

Użytkownicy będą zawierać następującą strukturę:

  • ID
  • Imię
  • nazwisko
  • e-mail
  • hasło
  • poziom uprawnień

Produkty będą zawierać następującą strukturę:

  • ID
  • imię
  • opis
  • Cena £

Jeśli chodzi o standard kodowania, w tym projekcie użyjemy TypeScript. W pliku źródłowym będziesz mógł skonfigurować wszystko, aby rozpocząć kodowanie za pomocą TypeScript.

Kod Miejmy!

Przede wszystkim upewnij się, że masz zainstalowaną najnowszą wersję Node.js. W momencie publikacji aktualna wersja to 10.15.3, zgodnie z Nodejs.org.

Inicjowanie projektu

Zacznijmy od nowego folderu, który możemy nazwać node-graphql . Tam możemy otworzyć terminal lub konsolę Git CLI i rozpocząć magię za pomocą następującego polecenia: npm init .

Konfigurowanie naszych zależności i TypeScript

Aby przyspieszyć ten proces, zastąpienie package.json następującym w naszym repozytorium Git powinno zawierać wszystkie niezbędne zależności:

 { "name": "node-graphql", "version": "1.0.0", "description": "", "main": "dist/index.js", "scripts": { "tsc": "tsc", "start": "npm run tsc && node ./build/app.js" }, "author": "", "license": "ISC", "dependencies": { "@types/express": "^4.16.1", "@types/express-graphql": "^0.6.2", "@types/graphql": "^14.0.7", "express": "^4.16.4", "express-graphql": "^0.7.1", "graphql": "^14.1.1", "graphql-tools": "^4.0.4" }, "devDependencies": { "tslint": "^5.14.0", "typescript": "^3.3.4000" } }

Po zaktualizowaniu package.json po prostu naciśnij ponownie terminal i użyj: npm install . Zainstaluje on wszystkie zależności potrzebne do uruchomienia API GraphQL w Node.js i Express.

Następnym elementem jest konfiguracja naszego trybu TypeScript. Potrzebujemy pliku o nazwie tsconfig.json w naszym folderze głównym z następującymi danymi:

 { "compilerOptions": { "target": "ES2016", "module": "commonjs", "outDir": "./build", "strict": true, "esModuleInterop": true } }

Logika kodu dla tej konfiguracji będzie obecna w folderze aplikacji. Tam możemy stworzyć plik app.ts i do podstawowych testów dodać tam następujący kod:

 console.log('Hello Graphql Node API tutorial');

Dzięki naszej konfiguracji możemy teraz uruchomić npm start i czekać na kompilację oraz móc przetestować, czy wszystko działa poprawnie. W konsoli terminala powinieneś zobaczyć nasz samouczek Hello GraphQL Node API. W tylnej scenie konfiguracja zasadniczo kompiluje kod TypeScript do czystego JavaScript, a następnie wykonuje naszą kompilację w folderze build .

Teraz skonfigurujmy podstawowy szkielet dla naszego GraphQL API. Aby rozpocząć nasz projekt, dodamy trzy podstawowe importy:

  • Wyrazić
  • Express-graphql
  • Narzędzia Graphql

Zacznijmy składać to wszystko razem:

 import express from 'express'; import graphqlHTTP from 'express-graphql'; import {makeExecutableSchema} from 'graphql-tools';

Teraz powinniśmy być w stanie zacząć trochę kodować. Następnym krokiem jest zajęcie się naszą aplikacją w Express i podstawową konfiguracją GraphQL, taką jak:

 import express from 'express'; import graphqlHTTP from 'express-graphql'; import {makeExecutableSchema} from 'graphql-tools'; const app: express.Application = express(); const port = 3000; let typeDefs: any = [` type Query { hello: String } type Mutation { hello(message: String) : String } `]; let helloMessage: String = 'World!'; let resolvers = { Query: { hello: () => helloMessage }, Mutation: { hello: (_: any, helloData: any) => { helloMessage = helloData.message; return helloMessage; } } }; app.use( '/graphql', graphqlHTTP({ schema: makeExecutableSchema({typeDefs, resolvers}), graphiql: true }) ); app.listen(port, () => console.log(`Node Graphql API listening on port ${port}!`));

Co robimy to:

  • Włączenie portu 3000 dla naszej aplikacji serwera Express.
  • Zdefiniowanie, które zapytania i mutacje chcemy wykorzystać jako szybki przykład.
  • Zdefiniowanie sposobu działania zapytań i mutacji.

OK, ale co się dzieje z typeDefs i resolverami, a także w odniesieniu do zapytań i mutacji?

  • typeDefs — definicja naszego schematu tego, czego możemy oczekiwać od zapytań i mutacji.
  • Resolvery — zamiast oczekiwania na pola lub wymagane parametry, tutaj definiujemy funkcje i zachowania, jak powinny działać zapytania i mutacje.
  • Zapytania — „pobiera”, które chcemy odczytać z serwera.
  • Mutacje - Nasze żądania, które wpłyną na wszelkie dane, które posiadamy na naszym własnym serwerze.

Teraz uruchommy ponownie npm start , aby zobaczyć, co tam mamy. Spodziewamy się, że aplikacja będzie działać z następującym komunikatem: Node Graphql API nasłuchuje na porcie 3000!

Możemy teraz spróbować odpytywać i testować GraphQL API na naszym własnym serwerze poprzez: http://localhost:3000/graphql

Samouczek GraphQL: test serwera

Świetnie, teraz możemy napisać nasze pierwsze własne zapytanie, które zostało zdefiniowane jako „cześć”.

Samouczek GraphQL: pierwsze zapytanie

Zauważ, że sposób, w jaki zdefiniowaliśmy to w typeDefs , strona może nam pomóc w zbudowaniu zapytania.

To świetnie, ale jak możemy zmienić wartość? Mutacje!

Zobaczmy teraz, co się stanie, gdy zmienimy naszą wartość w pamięci za pomocą mutacji:

Samouczek GraphQL: demonstracja mutacji

Teraz możemy wykonywać podstawowe operacje CRUD z naszym API GraphQL Node.js. Przejdźmy teraz do naszego kodu.

Produkty

W przypadku produktów użyjemy modułu o nazwie produkty. Aby uprościć ten artykuł, użyjemy bazy danych w pamięci tylko do celów demonstracyjnych. Zdefiniujemy model i usługę do zarządzania produktami.

Nasz model będzie się opierał w następujący sposób:

 export class Product { private id: Number = 0; private name: String = ''; private description: String = ''; private price: Number = 0; constructor(productId: Number, productName: String, productDescription: String, price: Number) { this.id = productId; this.name = productName; this.description = productDescription; this.price = price; } }

Usługa, która będzie komunikować się z GraphQL będzie zdefiniowana jako:

 export class ProductsService { public products: any = []; configTypeDefs() { let typeDefs = ` type Product { name: String, description: String, id: Int, price: Int } `; typeDefs += ` extend type Query { products: [Product] } `; typeDefs += ` extend type Mutation { product(name:String, id:Int, description: String, price: Int): Product! }`; return typeDefs; } configResolvers(resolvers: any) { resolvers.Query.products = () => { return this.products; }; resolvers.Mutation.product = (_: any, product: any) => { this.products.push(product); return product; }; } }

Użytkownicy

Dla użytkowników będziemy postępować zgodnie z tą samą strukturą, co moduł produktów. Będziemy mieli model i usługę dla użytkowników. Model zostanie zdefiniowany jako:

 export class User { private id: Number = 0; private firstName: String = ''; private lastName: String = ''; private email: String = ''; private password: String = ''; private permissionLevel: Number = 1; constructor(id: Number, firstName: String, lastName: String, email: String, password: String, permissionLevel: Number) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.email = email; this.password = password; this.permissionLevel = permissionLevel; } }

Tymczasem nasza usługa będzie wyglądała następująco:

 const crypto = require('crypto'); export class UsersService { public users: any = []; configTypeDefs() { let typeDefs = ` type User { firstName: String, lastName: String, id: Int, password: String, permissionLevel: Int, email: String } `; typeDefs += ` extend type Query { users: [User] } `; typeDefs += ` extend type Mutation { user(firstName:String, lastName: String, password: String, permissionLevel: Int, email: String, id:Int): User! }`; return typeDefs; } configResolvers(resolvers: any) { resolvers.Query.users = () => { return this.users; }; resolvers.Mutation.user = (_: any, user: any) => { let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(user.password).digest("base64"); user.password = hash; this.users.push(user); return user; }; } }

Przypominamy, że kod źródłowy jest dostępny do wykorzystania pod tym linkiem.

Teraz możemy grać i testować nasz kod. npm start . Będziemy mieli serwer działający na porcie 3000. Możemy teraz uzyskać dostęp do GraphQL do testowania pod adresem http://localhost:3000/graphql.

Spróbujmy mutacji, aby dodać element do naszej listy produktów:

Demonstracja mutacji Node.js GraphQL

Aby sprawdzić, czy zadziałało, użyjemy teraz zapytania o produkty, ale otrzymamy tylko id , name , i price :

 query{ products{ id, name, price } } The response will be: { "data": { "products": [ { "id": 100, "name": "My amazing product", "price": 400 } ] } }

I to wszystko; produkt działa zgodnie z oczekiwaniami. Teraz możemy grać i zmieniać pola, jeśli chcemy. Możesz spróbować dodać opis:

 query{ products{ id, name, description, price } }

Teraz mamy już opisy naszych produktów. Wypróbujmy teraz użytkowników.

 mutation{ user(id:200, firstName:"Marcos", lastName:"Silva", password:"amaz1ingP4ss", permissionLevel:9, email:"[email protected]") { id } }

A zapytanie będzie wyglądać tak:

 query{ users{ id, firstName, lastName, password, email } }

Z odpowiedzią taką jak:

 { "data": { "users": [ { "id": 200, "firstName": "Marcos", "lastName": "Silva", "password": "kpj6Mq0tGChGbZ+BT9Nw6RMCLReZEPPyBCaUS3X23lZwCCp1Ogb94/oqJlya0xOBdgEbUwqRSuZRjZGhCzLdeQ==", "email": "[email protected]" } ] } }

A teraz nasz szkielet GraphQL jest gotowy! Od tego momentu jest mnóstwo kroków w kierunku użytecznego, w pełni funkcjonalnego interfejsu API, ale podstawowy rdzeń jest już ustawiony.

Podsumowanie i myśli końcowe

Artykuł jest dość obszerny i zawiera wiele podstawowych informacji dotyczących rozwoju GraphQL Node.js API.

Przyjrzyjmy się, co omówiliśmy do tej pory:

  • Wykorzystanie Node.js z Express i GraphQL do budowy API GraphQL;
  • Podstawowe użycie GraphQL;
  • Podstawowe użycie zapytań i mutacji;
  • Podstawowe podejście do tworzenia modułów do Twojego projektu;
  • Testowanie naszego GraphQL API;

Aby bardziej skoncentrować się na rozwoju rzeczy, uniknęliśmy kilku ważnych elementów, które można pokrótce podsumować w następujący sposób:

  • Walidacje dla nowych pozycji;
  • Właściwa obsługa błędów za pomocą ogólnej usługi błędów;
  • Walidacja pól, których użytkownik może użyć przy każdym żądaniu za pomocą usługi ogólnej;
  • Dodaj przechwytywacz JWT, aby zabezpieczyć interfejs API;
  • Zarządzaj hashem hasła w bardziej efektywny sposób;
  • Dodaj testy jednostkowe i integracyjne;

Pamiętaj, że pełny kod źródłowy znajduje się pod tym linkiem Git. Nie krępuj się używać, rozwidlać, otwierać problemy, wysyłać żądania ściągnięcia i bawić się tym! Należy pamiętać, że wszystkie standardy i sugestie zawarte w tym artykule nie są wyryte w kamieniu.

To tylko jedno z wielu podejść, które można wykorzystać do rozpoczęcia projektowania własnego API GraphQL. Pamiętaj też, aby przeczytać i poznać GraphQL bardziej szczegółowo, dowiedzieć się, co ma do zaoferowania i jak może jeszcze bardziej ulepszyć Twoje interfejsy API.