Ο πλήρης οδηγός για τη δημιουργία ενός API με TypeScript και AWS

Σε αυτό το άρθρο θα δούμε πώς μπορούμε γρήγορα και εύκολα να δημιουργήσουμε ένα API με TypeScript και χωρίς διακομιστή.

Στη συνέχεια θα μάθουμε πώς να χρησιμοποιούμε το aws-sdk για πρόσβαση σε άλλες υπηρεσίες AWS και για τη δημιουργία ενός αυτόματου API μετάφρασης.

Αν προτιμάτε να παρακολουθείτε και να μάθετε, μπορείτε να δείτε το παρακάτω βίντεο:

Ξεκινώντας

Για να ξεκινήσουμε όλη αυτή τη διαδικασία, πρέπει να βεβαιωθούμε ότι έχουμε εγκαταστήσει το Serverless Framework και ότι έχουμε ρυθμίσει ένα προφίλ AWS στον υπολογιστή μας. Εάν δεν το έχετε κάνει τότε μπορείτε να δείτε αυτό το βίντεο σχετικά με τον τρόπο ρύθμισης όλων αυτών.

Αν θέλετε να ακολουθήσετε αυτό το σεμινάριο, μπορείτε να ακολουθήσετε όλα τα βήματα ή να κατεβάσετε τον κώδικα εδώ και να ακολουθήσετε τον ολοκληρωμένο κωδικό.

Τώρα αρχίζουμε να δημιουργούμε το έργο και το API χωρίς διακομιστές. Πρέπει να ξεκινήσουμε από ένα τερματικό και να εκτελέσουμε την εντολή για να δημιουργήσουμε το νέο μας repo. Το μόνο που χρειάζεται να κάνετε είναι να απενεργοποιήσετε {YOUR FOLDER NAME}το όνομα του φακέλου σας.

serverless create --template aws-nodejs-typescript --path {YOUR FOLDER NAME}

Αυτό θα δημιουργήσει ένα πολύ βασικό έργο χωρίς διακομιστή με TypeScript. Εάν ανοίξουμε αυτόν τον νέο φάκελο με κώδικα VS, τότε μπορούμε να δούμε τι μας έχει δώσει το πρότυπο.

Τα κύρια αρχεία που θέλουμε να δούμε είναι το serverless.tsαρχείο και το handler.tsαρχείο.

Το serverless.tsαρχείο είναι όπου διατηρείται η διαμόρφωση για την ανάπτυξη. Αυτό το αρχείο λέει στο πλαίσιο χωρίς διακομιστή το όνομα του έργου, τη γλώσσα χρόνου εκτέλεσης του κώδικα, τη λίστα των λειτουργιών και μερικές άλλες επιλογές διαμόρφωσης.

Όποτε θέλουμε να αλλάξουμε την αρχιτεκτονική του έργου μας, αυτό είναι το αρχείο στο οποίο εργαζόμαστε.

Το επόμενο αρχείο είναι το handler.tsαρχείο. Εδώ έχουμε το παράδειγμα κώδικα για ένα λάμδα που μας έχει δοθεί από το πρότυπο. Είναι πολύ βασικό και επιστρέφει απλώς μια απόκριση API Gateway με ένα μήνυμα και το συμβάν εισαγωγής. Θα το χρησιμοποιήσουμε αργότερα ως αφετηρία για το δικό μας API.

Δημιουργήστε το δικό σας λάμδα

Τώρα που έχουμε δει τι παίρνουμε με το πρότυπο, ήρθε η ώρα να προσθέσουμε το δικό μας τελικό σημείο Lambda και API.

Για να ξεκινήσετε, θα φτιάξουμε έναν νέο φάκελο για να διατηρήσουμε όλο τον κώδικα λάμδα και να τον καλέσουμε lambdas. Αυτό βοηθά να το οργανώσετε, ειδικά όταν αρχίζετε να παίρνετε μερικά διαφορετικά λάμδα σε ένα έργο.

Σε αυτόν τον νέο φάκελο θα δημιουργήσουμε το νέο λάμδα που το ονομάζουμε getCityInfo.ts. Εάν ανοίξουμε αυτό το αρχείο, μπορούμε να αρχίσουμε να δημιουργούμε τον κωδικό μας. Μπορούμε να ξεκινήσουμε με την αντιγραφή όλου του handler.tsκώδικα ως σημείο εκκίνησης.

Το πρώτο πράγμα που πρόκειται να κάνουμε είναι να αλλάξουμε το όνομα της συνάρτησης σε handler. Αυτή είναι μια προσωπική προτίμηση, αλλά μου αρέσει να ονομάζω τη λειτουργία που χειρίζεται τον χειριστή του συμβάντος.

Στην πρώτη γραμμή σε αυτήν τη λειτουργία πρέπει να προσθέσουμε κάποιο κωδικό για να πάρουμε την πόλη που ζητά ο χρήστης. Μπορούμε να το πάρουμε από τη διαδρομή URL χρησιμοποιώντας pathParameters.

const city = event.pathparameter?.city;

Ένα πράγμα που μπορεί να παρατηρήσετε είναι η χρήση αυτής της ?.δήλωσης. Αυτή είναι η προαιρετική αλυσίδα και είναι ένα πολύ ωραίο χαρακτηριστικό. Εγώ

t σημαίνει ότι εάν η παράμετρος διαδρομής είναι αληθινή τότε πάρτε την παράμετρο πόλης, αλλιώς επιστρέψτε ακαθόριστη Αυτό σημαίνει ότι εάν pathParameterδεν ήταν αντικείμενο , αυτό δεν θα λάβει το σφάλμα που προκαλεί το σφάλμα χρόνου εκτέλεσης του κόμβου.cannot read property city of undefined

Τώρα που έχουμε την πόλη πρέπει να ελέγξουμε ότι η πόλη είναι έγκυρη και ότι έχουμε δεδομένα για αυτήν την πόλη. Για αυτό χρειαζόμαστε ορισμένα δεδομένα. Μπορούμε να χρησιμοποιήσουμε τον παρακάτω κώδικα και να τον επικολλήσουμε στο κάτω μέρος του αρχείου.

interface CityData { name: string; state: string; description: string; mayor: string; population: number; zipCodes?: string; } const cityData: { [key: string]: CityData } = { newyork: { name: 'New York', state: 'New York', description: 'New York City comprises 5 boroughs sitting where the Hudson River meets the Atlantic Ocean. At its core is Manhattan, a densely populated borough that’s among the world’s major commercial, financial and cultural centers. Its iconic sites include skyscrapers such as the Empire State Building and sprawling Central Park. Broadway theater is staged in neon-lit Times Square.', mayor: 'Bill de Blasio', population: 8399000, zipCodes: '100xx–104xx, 11004–05, 111xx–114xx, 116xx', }, washington: { name: 'Washington', state: 'District of Columbia', description: `DescriptionWashington, DC, the U.S. capital, is a compact city on the Potomac River, bordering the states of Maryland and Virginia. It’s defined by imposing neoclassical monuments and buildings – including the iconic ones that house the federal government’s 3 branches: the Capitol, White House and Supreme Court. It's also home to iconic museums and performing-arts venues such as the Kennedy Center.`, mayor: 'Muriel Bowser', population: 705549, }, seattle: { name: 'Seattle', state: 'Washington', description: `DescriptionSeattle, a city on Puget Sound in the Pacific Northwest, is surrounded by water, mountains and evergreen forests, and contains thousands of acres of parkland. Washington State’s largest city, it’s home to a large tech industry, with Microsoft and Amazon headquartered in its metropolitan area. The futuristic Space Needle, a 1962 World’s Fair legacy, is its most iconic landmark.`, mayor: 'Jenny Durkan', population: 744955, }, };

Η διαφορά μεταξύ αυτού και του JavaScript είναι ότι μπορούμε να δημιουργήσουμε μια διεπαφή για να πούμε στο σύστημα ποια πρέπει να είναι η δομή των δεδομένων. Αυτό φαίνεται σαν επιπλέον δουλειά στην αρχή, αλλά θα σας βοηθήσει να κάνετε τα πάντα ευκολότερα αργότερα.

Μέσα στη διεπαφή μας ορίζουμε τα κλειδιά του αντικειμένου της πόλης. Μερικές που είναι συμβολοσειρές, ένας αριθμός και μετά zipCodesείναι μια προαιρετική ιδιότητα. Αυτό σημαίνει ότι θα μπορούσε να είναι εκεί αλλά δεν πρέπει να είναι.

Αν θέλουμε να δοκιμάσουμε τη διεπαφή μας, μπορούμε να δοκιμάσουμε να προσθέσουμε μια νέα ιδιότητα σε οποιαδήποτε από τις πόλεις στα δεδομένα της πόλης μας.

Το TypeScript θα πρέπει αμέσως να σας πει ότι η νέα σας ιδιότητα δεν υπάρχει στη διεπαφή. Εάν διαγράψετε μια από τις απαιτούμενες ιδιότητες, το TypeScript θα παραπονεθεί επίσης. Αυτό διασφαλίζει ότι έχετε πάντα τα σωστά δεδομένα και τα αντικείμενα φαίνονται πάντα ακριβώς όπως αναμενόταν.

Τώρα που έχουμε τα δεδομένα, μπορούμε να ελέγξουμε εάν ο χρήστης έστειλε το σωστό αίτημα πόλης.

if (!city || !cityData[city]) { }

Εάν αυτή η δήλωση είναι αληθής, τότε ο χρήστης έχει κάνει κάτι λάθος, επομένως πρέπει να επιστρέψουμε μια απάντηση 400.

Θα μπορούσαμε απλά να πληκτρολογήσουμε με μη αυτόματο τρόπο τον κώδικα εδώ, αλλά πρόκειται να δημιουργήσουμε ένα νέο apiResponsesαντικείμενο με μεθόδους για μερικούς από τους πιθανούς κωδικούς απόκρισης API.

const apiResponses = { _200: (body: { [key: string]: any }) => { return { statusCode: 200, body: JSON.stringify(body, null, 2), }; }, _400: (body: { [key: string]: any }) => { return { statusCode: 400, body: JSON.stringify(body, null, 2), }; }, };

Αυτό απλώς διευκολύνει την επαναχρησιμοποίηση αργότερα στο αρχείο. Θα πρέπει επίσης να δείτε ότι έχουμε μια ιδιοκτησία του body: { [key: string]: any }. Αυτό δηλώνει ότι αυτή η συνάρτηση έχει μια ιδιότητα σώματος που πρέπει να είναι αντικείμενο. Αυτό το αντικείμενο μπορεί να έχει κλειδιά που έχουν αξία οποιουδήποτε τύπου.

Επειδή γνωρίζουμε ότι bodyπρόκειται πάντα να είναι μια συμβολοσειρά που μπορούμε να χρησιμοποιήσουμε JSON.stringifyγια να διασφαλίσουμε ότι θα επιστρέψουμε ένα κορδόνι.

Εάν προσθέσουμε αυτήν τη λειτουργία στον χειριστή μας, έχουμε αυτό:

export const handler: APIGatewayProxyHandler = async (event, _context) => { const city = event.pathParameters?.city; if (!city || !cityData[city]) { return apiResponses._400({ message: 'missing city or no data for that city' }); } return apiResponses._200(cityData[city]); };

Εάν ο χρήστης δεν διέσχισε μια πόλη ή δεν διέθεσε μια πόλη για την οποία δεν έχουμε δεδομένα, επιστρέφουμε ένα 400 με ένα μήνυμα σφάλματος. Εάν τα δεδομένα υπάρχουν τότε επιστρέφουμε 200 με ένα σώμα των δεδομένων.

Προσθήκη νέου API μετάφρασης

Στην προηγούμενη ενότητα δημιουργήσαμε το Repo API TypeScript και δημιουργήσαμε ένα λάμδα που μόλις χρησιμοποίησε σκληρά κωδικοποιημένα δεδομένα.

Αυτό το μέρος θα σας διδάξει πώς να χρησιμοποιήσετε το aws-sdk για άμεση αλληλεπίδραση με άλλες υπηρεσίες AWS για τη δημιουργία ενός πραγματικά ισχυρού API.

Για να ξεκινήσουμε, πρέπει να προσθέσουμε ένα νέο αρχείο για το API μετάφρασης. Δημιουργήστε ένα νέο αρχείο κάτω από το lambdasφάκελο που ονομάζεται translate.ts. Μπορούμε να ξεκινήσουμε αυτό το αρχείο με κάποιο βασικό κωδικό boilerplate. Αυτός είναι ο αρχικός κώδικας για το TypeScript API Lambda.

import { APIGatewayProxyHandler } from 'aws-lambda'; import 'source-map-support/register'; export const handler: APIGatewayProxyHandler = async (event) => { };

Τώρα πρέπει να λάβουμε το κείμενο στο οποίο ο χρήστης θέλει να μεταφραστεί και τη γλώσσα στην οποία θέλει να μεταφράσει. Μπορούμε να τα πάρουμε από το κύριο μέρος του αιτήματος.

Ένα επιπλέον πράγμα που πρέπει να κάνουμε εδώ είναι να αναλύσουμε το σώμα. Από προεπιλογή, το API Gateway επιδιορθώνει οποιοδήποτε JSON περνά στο σώμα. Στη συνέχεια μπορούμε να καταστρέψουμε το κείμενο και τη γλώσσα από το σώμα.

const body = JSON.parse(event.body); const { text, language } = body;

Τώρα πρέπει να ελέγξουμε ότι ο χρήστης έχει περάσει κείμενο και γλώσσα.

if (!text) { // retrun 400 } if (!language) { // return 400 }

Στο τελευταίο μέρος δημιουργήσαμε την απόκριση 400 ως συνάρτηση στο αρχείο. Καθώς πρόκειται να χρησιμοποιήσουμε αυτές τις απαντήσεις API σε πολλά αρχεία, είναι καλή ιδέα να τα βγάλουμε έξω στο κοινό τους αρχείο.

Δημιουργήστε έναν νέο φάκελο με το όνομα lambdascommon . Εδώ θα αποθηκεύσουμε όλες τις κοινές λειτουργίες.

Σε αυτόν το φάκελο δημιουργήστε ένα νέο αρχείο που ονομάζεται apiResponses.ts. Αυτό το αρχείο πρόκειται να εξαγάγει το apiResponsesαντικείμενο με τις μεθόδους _200 και _400 σε αυτό. Εάν πρέπει να επιστρέψετε άλλους κωδικούς απόκρισης, τότε μπορείτε να τους προσθέσετε σε αυτό το αντικείμενο.

const apiResponses = { _200: (body: { [key: string]: any }) => { return { statusCode: 200, body: JSON.stringify(body, null, 2), }; }, _400: (body: { [key: string]: any }) => { return { statusCode: 400, body: JSON.stringify(body, null, 2), }; }, }; export default apiResponses;

Τώρα μπορούμε να εισαγάγουμε αυτό το αντικείμενο στον κώδικά μας και να χρησιμοποιήσουμε αυτές τις κοινές μεθόδους στον κώδικά μας. Στην κορυφή του αρχείου translate.ts μπορούμε πλέον να προσθέσουμε αυτήν τη γραμμή:

import apiResponses from './common/apiResponses';

και ενημερώστε τους ελέγχους κειμένου και γλώσσας για να καλέσετε τη μέθοδο _400 σε αυτό το αντικείμενο:

if (!text) { return apiResponses._400({ message: 'missing text fom the body' }); } if (!language) { return apiResponses._400({ message: 'missing language from the body' }); }

Με αυτό που ολοκληρώθηκε, γνωρίζουμε ότι έχουμε το κείμενο για μετάφραση και μια γλώσσα για μετάφραση, ώστε να μπορούμε να ξεκινήσουμε τη διαδικασία μετάφρασης.

Η χρήση του aws-sdk είναι σχεδόν πάντα μια εργασία ασύγχρονου, οπότε θα το τυλίξουμε σε μια δοκιμή / catch, έτσι ώστε ο χειρισμός των σφαλμάτων μας να είναι ευκολότερος.

try { } catch (error) { }

Το πρώτο πράγμα που πρέπει να κάνουμε είναι να εισαγάγουμε το aws-sdk και να δημιουργήσουμε μια νέα παρουσία της υπηρεσίας μετάφρασης.

Για να το κάνουμε αυτό πρέπει να εγκαταστήσουμε το aws-sdk και μετά να το εισαγάγουμε. Πρώτα εκτελέστε npm install --save aws-sdkκαι, στη συνέχεια, προσθέστε αυτόν τον κωδικό στην κορυφή του αρχείου μετάφρασης:

import * as AWS from 'aws-sdk'; const translate = new AWS.Translate();

Με αυτό μπορούμε να αρχίσουμε να γράφουμε τον μεταφραστικό μας κώδικα. Θα ξεκινήσουμε με τη γραμμή που κάνει τη μετάφραση πρώτα. Προσθέστε το στην ενότητα δοκιμής .

const translatedMessage = await translate.translateText(translateParams).promise();

One thing that some of you may have noticed is that we're passing in translateParams without having defined it yet. That is because we're not sure what type it is yet.

To find this out we can use a tool in VS Code called go to definition. This allows us to jump to where the function if defined so we can find out what the type of the parameters is. You can either right click and select go to definition or hold Ctrl and click on the function.

As you can see the translateText function takes a param of Translate.Types.TranslateTextRequest.

Another way to find this out is to use intelisense by mousing over the translateText function. You should see this, where you can see that params: AWS.Translate.TranslateTextRequest:

With this we can create our translate params above the translate request we made earlier. We can then populate it based on the type we are setting it as. This makes sure we're passing up the correct fields.

const translateParams: AWS.Translate.Types.TranslateTextRequest = { Text: text, SourceLanguageCode: 'en', TargetLanguageCode: language, };

Now that we have the parameters and are passing them into the translate.translateText function, we can start creating our response. This is just going to be a 200 response with the translated message.

return apiResponses._200({ translatedMessage });

With that all done we can move onto the catch section. In here we just want to log out the error and then return a 400 response from the common file.

console.log('error in the translation', error); return apiResponses._400({ message: 'unable to translate the message' });

With that completed we're done with our lambda code, so need to move into our severless.ts file to add this new API endpoint and give it the permissions it needs.

In the serverless.ts file we can scroll down to the functions section. In here we need to add a new function to the object.

translate: { handler: 'lambdas/translate.handler', events: [ { http: { path: 'translate', method: 'POST', cors: true, }, }, ], },

The main difference between this and the previous endpoint is that the endpoint is now a POST method. This means if you try and do a GET request to this URL path, you'll get an error response.

The last thing to do is to give the lambdas permission to use the Translate service. With almost all of the AWS Services, you'll need to add extra permissions to be able to use the from within a lambda.

To do this we add a new field onto the provider section called iamRoleStatements. This is an array of allow or deny statements for different services and resources.

iamRoleStatements: [ { Effect: 'Allow', Action: ['translate:*'], Resource: '*', }, ],

With this added in we have everything we need set up so we can run sls deploy to deploy our new API.

Once this has deployed, we can get the API URL and use a tool like postman or postwoman.io to make a request to that URL. We just need to pass up a body of:

{ "text": "This is a test message for translation", "language": "fr" }

and then we should get a 200 response of:

{ "translatedMessage": { "TranslatedText": "Ceci est un message de test pour la traduction", "SourceLanguageCode": "en", "TargetLanguageCode": "fr" } }

Summary

In this article we've learnt how to:

  • Set up a new TypeScript repo with severless create --template aws-nodejs-typescript
  • Add our own Lambda that returns a selection of hardcoded data
  • Added that Lambda as an API endpoint
  • Added another Lambda which will automatically translate any text passed to it
  • Added an API endpoint and gave the Lambda the permissions it needed to work

Αν σας άρεσε αυτό το άρθρο και θέλετε να μάθετε περισσότερα σχετικά με το Serverless και το AWS, τότε έχω ένα κανάλι Youtube με πάνω από 50 βίντεο για όλα αυτά. Θα συνιστούσα να παρακολουθώ τα βίντεο που θεωρείτε πιο ενδιαφέροντα στη λίστα αναπαραγωγής μου χωρίς διακομιστές και AWS.