A teljes útmutató egy API létrehozásához TypeScript és AWS használatával

Ebben a cikkben megvizsgáljuk, hogyan lehet gyorsan és egyszerűen felépíteni egy API-t a TypeScript és a Serverless segítségével.

Ezután megtanuljuk az aws-sdk használatát más AWS-szolgáltatások eléréséhez és egy automatikus fordítási API létrehozásához.

Ha inkább nézni és tanulni szeretne, megnézheti az alábbi videót:

Elkezdeni

Ennek az egész folyamatnak a megkezdéséhez meg kell győződnünk arról, hogy telepítve van a Serverless Framework, és van egy AWS-profil a számítógépünkön. Ha még nem tette meg, akkor nézze meg ezt a videót arról, hogyan állíthatja be mindezt.

Ha követni szeretné ezt az oktatóanyagot, kövesse az összes lépést, vagy töltse le a kódot itt, és kövesse a kitöltött kóddal.

Most nekünk áll a szerver nélküli projekt és az API létrehozása. Egy terminálban kell elindulnunk, és futtatnunk kell a parancsot az új repo létrehozásához. Csak annyit kell tennie, hogy kikapcsolja {YOUR FOLDER NAME}a mappának a nevét.

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

Ez egy nagyon egyszerű, szerver nélküli projektet hoz létre a TypeScript segítségével. Ha megnyitjuk ezt az új mappát a VS kóddal, akkor láthatjuk, hogy a sablon mit adott nekünk.

A fő fájlok, amelyeket meg akarunk nézni, a serverless.tsfájl és a handler.tsfájl.

A serverless.tsfájl tárolja a telepítés konfigurációját. Ez a fájl megmondja a szerver nélküli keretrendszernek a projekt nevét, a kód futási nyelvét, a függvények listáját és néhány további konfigurációs lehetőséget.

Amikor meg akarjuk változtatni a projektünk architektúráját, ez az a fájl, amelyben dolgozni fogunk.

A következő fájl a handler.tsfájl. Itt van a lambda példakódja, amelyet a sablon adott nekünk. Nagyon egyszerű, és csak egy API Gateway választ ad vissza egy üzenettel és a bemeneti eseménypel. Ezt később a saját API-junk kezdő blokkaként fogjuk használni.

Készítse el saját lambdáját

Most, hogy láttuk, mit kapunk a sablonnal, itt az ideje hozzáadni a saját Lambda és API végpontunkat.

Először egy új mappát fogunk készíteni, amely az összes lambda kódot megtartja és felhívja lambdas. Ez segít megszervezni, különösen, ha egy projektben néhány különböző lambdát kezd el kapni.

Abban az új mappában létrehozzuk az új lambdánkat, hívva getCityInfo.ts. Ha kinyitjuk ezt a fájlt, elkezdhetjük létrehozni a kódunkat. Kezdhetjük úgy, hogy az egész handler.tskódot kiindulási pontként másoljuk .

Az első dolog, amit meg fogunk tenni, a funkció nevének megváltoztatása handler. Ez személyes preferencia, de szeretem megnevezni azt a funkciót, amely az eseménykezelőt kezeli.

A függvény első sorában hozzá kell adnunk néhány kódot, hogy megkapjuk a várost, amelyet a felhasználó kér. Ezt az URL elérési útjáról a pathParameters.

const city = event.pathparameter?.city;

Egy dolog, amit észrevehet, az a ?.nyilatkozat használata. Ez opcionális láncolás és nagyon jó funkció. én

t azt jelenti, hogy ha az útparaméter igaz, akkor kapd meg a városi paramétert, különben pedig definiálatlanul tér vissza. Ez azt jelenti, hogy ha pathParameternem volt objektum , akkor nem kapta meg azt a hibát, amely a Csomópont futásidejének hibáját okozza.cannot read property city of undefined

Most, hogy megvan a város, ellenőriznünk kell, hogy a város érvényes-e és van-e adatunk arra a városra. Ehhez szükségünk van néhány adatra. Használhatjuk az alábbi kódot, és beilleszthetjük a fájl aljára.

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

A különbség ez és a JavaScript között az, hogy létrehozhatunk egy felületet, amely megmondja a rendszernek, hogy mi legyen az adatok szerkezete. Ez már az elején extra munkának tűnik, de később mindent megkönnyít.

Interfészünkön belül meghatározzuk a város objektumának kulcsait; némelyik karakterlánc, egy szám, majd az zipCodesopcionális tulajdonság. Ez azt jelenti, hogy ott lehet, de nem kell.

Ha tesztelni akarjuk a felületünket, megpróbálhatunk új tulajdonságot adni a városi adatok bármelyik városához.

A TypeScript-nek azonnal meg kell mondania, hogy az új tulajdonság nem létezik a felületen. Ha törli a szükséges tulajdonságok egyikét, a TypeScript is panaszt fog tenni. Ez biztosítja, hogy mindig megfelelő adatokkal rendelkezzen, és az objektumok mindig pontosan úgy nézzenek ki, mint az elvárták.

Most, hogy rendelkezünk az adatokkal, ellenőrizhetjük, hogy a felhasználó elküldte-e a helyes városi kérést.

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

Ha ez az állítás igaz, akkor a felhasználó valamit rosszul tett, ezért 400-as választ kell adnunk.

Csak kézzel írhatnánk ide a kódot, de egy új apiResponsesobjektumot fogunk létrehozni a lehetséges API-válaszkódok közül néhány módszerével.

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

Ez csak sokkal könnyebbé teszi a fájl későbbi újrafelhasználását. Azt is látnia kell, hogy egyetlen tulajdonságunk van body: { [key: string]: any }. Ez azt állítja, hogy ennek a függvénynek van egy testtulajdonsága, amelynek objektumnak kell lennie. Az objektumnak lehetnek kulcsai, amelyek bármilyen típusú értékkel rendelkeznek.

Mivel tudjuk, hogy bodyez mindig egy karakterlánc lesz JSON.stringify, amellyel megbizonyosodhatunk arról, hogy visszaadunk egy karakterláncot.

Ha hozzáadjuk ezt a funkciót a kezelőhöz, akkor ezt kapjuk:

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

Ha a felhasználó nem haladt el egy városon, vagy elhaladt egy olyan városon, amelyre nincs adatunk, akkor egy 400-at adunk vissza hibaüzenettel. Ha az adatok léteznek, akkor egy 200-at adunk vissza az adatok törzsével.

Új fordítási API hozzáadása

Az előző részben beállítottuk a TypeScript API repót, és létrehoztunk egy lambdát, amely csak keményen kódolt adatokat használt.

This part is going to teach you how to use the aws-sdk to interact directly with other AWS services to create a really powerful API.

To start, we need to add a new file for our translation API. Create a new file under the lambdas folder called translate.ts. We can start this file out with some basic boilerplate code. This is the starting code for a TypeScript API Lambda.

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

Now we need to get the text that the user wants translated and the language that they want to translate to. We can get these from the body of the request.

One extra thing we have to do here is to parse the body. By default, API Gateway stringifies any JSON passed in the body. We can then destructure the text and language from the body.

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

We now need to check that the user has passed up text and language.

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

If you enjoyed this article and want to learn more about Serverless and AWS, then I have a Youtube Channel with over 50 videos on all of this. I'd recommend watching the videos you find most interesting in my Serverless and AWS playlist.