Den komplette guide til opbygning af en API med TypeScript og AWS

I denne artikel vil vi se på, hvordan vi hurtigt og nemt kan opbygge en API med TypeScript og Serverless.

Vi lærer derefter, hvordan du bruger aws-sdk til at få adgang til andre AWS-tjenester og oprette en automatisk oversættelses-API.

Hvis du foretrækker at se og lære, kan du tjekke videoen nedenfor:

Kom godt i gang

For at starte hele denne proces skal vi sørge for, at vi har Serverless Framework installeret og har en AWS-profil oprettet på vores computer. Hvis du ikke har det, kan du tjekke denne video om, hvordan du får det hele konfigureret.

Hvis du vil følge denne vejledning, kan du følge alle trin eller downloade koden her og følge med den færdige kode.

Nu skal vi oprette vores serverløse projekt og API. Vi er nødt til at starte i en terminal og køre kommandoen for at oprette vores nye repo. Alt hvad du skal gøre er at skifte {YOUR FOLDER NAME}for navnet på din mappe.

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

Dette vil skabe et meget grundlæggende serverløst projekt med TypeScript. Hvis vi åbner denne nye mappe med VS-kode, kan vi se, hvad skabelonen har givet os.

De vigtigste filer, vi vil se på, er serverless.tsfilen og handler.tsfilen.

Den serverless.tsfil er, hvor konfigurationen for indsættelsen holdes. Denne fil fortæller den serverløse ramme projektnavnet, kørselssprog for koden, listen over funktioner og et par andre konfigurationsmuligheder.

Når vi vil ændre arkitekturen i vores projekt, er det den fil, vi arbejder i.

Den næste fil er handler.tsfilen. Her har vi eksempelkoden for en lambda, som skabelonen giver os. Det er meget grundlæggende og returnerer bare et API Gateway-svar med en besked og inputhændelsen. Vi bruger dette senere som en startblok for vores egen API.

Opret din egen Lambda

Nu hvor vi har set, hvad vi får med skabelonen, er det tid til at tilføje vores eget Lambda- og API-slutpunkt.

For at starte skal vi oprette en ny mappe til at indeholde al vores lambda-kode og kalde den lambdas. Dette hjælper med at organisere det, især når du begynder at få et par forskellige lambdas i et projekt.

I den nye mappe skal vi oprette vores nye lambda, der kalder det getCityInfo.ts. Hvis vi åbner denne fil, kan vi begynde at oprette vores kode. Vi kan begynde med at kopiere hele handler.tskoden som udgangspunkt.

Den første ting, vi skal gøre, er at ændre funktionens navn til handler. Dette er en personlig præference, men jeg kan godt lide at navngive den funktion, der håndterer begivenhedshåndtereren.

På den første linje i denne funktion skal vi tilføje en kode for at få den by, som brugeren anmoder om. Vi kan få dette fra URL-stien ved hjælp af pathParameters.

const city = event.pathparameter?.city;

En ting, du muligvis bemærker, er brugen af ?.denne erklæring. Det er valgfri kæde og er en rigtig cool funktion. jeg

t betyder, at hvis sti-parameteren er sand, skal du få by-parameteren, ellers returnere udefineret. Dette betyder, at hvis det pathParameterikke var et objekt , ville dette ikke få den fejl, der får Node-runtime til at fejle.cannot read property city of undefined

Nu hvor vi har byen, skal vi kontrollere, at byen er gyldig, og at vi har data for den by. Til dette har vi brug for nogle data. Vi kan bruge koden nedenfor og indsætte den i bunden af ​​filen.

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, }, };

Forskellen mellem dette og JavaScript er, at vi kan oprette en grænseflade til at fortælle systemet, hvad datastrukturen skal være. Dette føles som ekstra arbejde i starten, men hjælper med at gøre alt lettere senere.

Inde i vores grænseflade definerer vi nøglerne til byobjektet; nogle der er strenge, et nummer, og derefter zipCodeser det en valgfri egenskab. Dette betyder, at det kunne være der, men ikke behøver at være.

Hvis vi vil teste vores grænseflade, kan vi prøve at tilføje en ny ejendom til nogen af ​​byerne i vores bydata.

TypeScript skal straks fortælle dig, at din nye ejendom ikke findes på grænsefladen. Hvis du sletter en af ​​de krævede egenskaber, vil TypeScript også klage. Dette sikrer, at du altid har de korrekte data, og objekter altid ser nøjagtigt ud som forventet.

Nu hvor vi har de data, kan vi kontrollere, om brugeren sendte den korrekte byanmodning.

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

Hvis denne erklæring er sand, har brugeren gjort noget forkert, derfor er vi nødt til at returnere et 400-svar.

Vi kunne bare indtaste koden manuelt her, men vi opretter et nyt apiResponsesobjekt med metoder til et par af de mulige API-svarkoder.

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), }; }, };

Dette gør det bare meget lettere at genbruge senere i filen. Du skal også se, at vi har en ejendom af body: { [key: string]: any }. Dette angiver, at denne funktion har en egenskab af krop, der skal være et objekt. Objektet kan have nøgler, der har en værdi af enhver type.

Fordi vi ved, at bodydet altid vil være en streng, kan vi bruge JSON.stringifytil at sikre, at vi returnerer en strengtekst.

Hvis vi tilføjer denne funktion til vores handler, får vi denne:

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]); };

Hvis brugeren ikke passerede en by eller passerede en, vi ikke har data til, returnerer vi en 400 med en fejlmeddelelse. Hvis der findes data, returnerer vi en 200 med en del af dataene.

Tilføjelse af en ny oversættelses-API

I det forrige afsnit oprettede vi vores TypeScript API repo og oprettede en lambda, som netop brugte hårdt kodede data.

Denne del vil lære dig, hvordan du bruger aws-sdk til at interagere direkte med andre AWS-tjenester for at skabe en virkelig kraftig API.

For at starte skal vi tilføje en ny fil til vores oversættelses-API. Opret en ny fil under den lambdaskaldte mappe translate.ts. Vi kan starte denne fil med nogle grundlæggende kedelpladekoder. Dette er startkoden til en TypeScript API Lambda.

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

Nu er vi nødt til at få den tekst, som brugeren ønsker oversat, og det sprog, som de vil oversætte til. Vi kan få disse fra anmodningen.

En ekstra ting, vi skal gøre her, er at analysere kroppen. Som standard strenger API Gateway enhver JSON, der er sendt i kroppen. Vi kan så ødelægge teksten og sproget fra kroppen.

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

Vi skal nu kontrollere, at brugeren har videregivet tekst og sprog.

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

In the last part we created the 400 response as a function in the file. As we're going to be using these API responses across multiple files, it is a good idea to pull them out to their own common file.

Create a new folder under lambdas called common. This is where we are going to store all common functions.

In that folder create a new file called apiResponses.ts. This file is going to export the apiResponses object with the _200 and _400 methods on it. If you have to return other response codes then you can add them to this object.

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;

We can now import that object into our code and use these common methods in our code. At the top of our translate.ts file we can now add this line:

import apiResponses from './common/apiResponses';

and update our text and language checks to call the _400 method on that object:

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

With that completed we know that we have the text to translate and a language to translate into, so we can start the translation process.

Using the aws-sdk is almost always an async task so we're going to wrap it in a try/catch so that our error handling is easier.

try { } catch (error) { }

The first thing we need to do is to import the aws-sdk and create a new instance of the translate service.

To do that we need to install the aws-sdk and then import it. First run npm install --save aws-sdk and then add this code to the top of your translate file:

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

With this we can start to write our translation code. We're going to start with the line that does the translation first. Add this in the try section.

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

Hvis du kunne lide denne artikel og vil lære mere om Serverless og AWS, har jeg en Youtube-kanal med over 50 videoer om alt dette. Jeg vil anbefale at se de videoer, du finder mest interessante i min Serverless og AWS playliste.