Exercise - Create functions for the Azure Functions app
In this unit, you create and configure functions in the Azure Functions app for the GET
, POST
, PUT
, and DELETE
endpoints in the Node.js Express app.
Add data access to the GET function
You created the first API endpoint when you created the Azure Functions app in the last unit. This function executes when an HTTP GET
is requested on /vacations
. You need to update the boilerplate code to call the data service to get the vacations.
Open the functions/src/functions/getVacations.ts file.
Open the server/routes/vacation.routes.ts file in a separate window so you can see both files side by side.
In getVacations.ts, add the vacationService import statement.
import { vacationService } from '../services';
In getVacations.ts, edit the
getVacations
function to call the vacationService.export async function getVacations(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function processed request for url "${request.url}"`); return { jsonBody: vacationService.getVacations() }; // Data access logic within the return object };
You could stop there. That is the only code you need to add to the function to get the vacations. However, you should also provide code to handle errors and return a status code. Update the function to use the following code.
export async function getVacations(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function processed request for url "${request.url}"`); try { const vacations = vacationService.getVacations(); // Data access logic if (vacations) { return { status: 200, jsonBody: vacations }; } else { return { status: 404, jsonBody: { error: 'No vacations found' } }; } } catch (error: unknown) { const err = error as Error; context.error(`Error listing vacations: ${err.message}`); return { status: 500, jsonBody: { error: 'Failed to list vacations' } }; } };
Organize the Azure Functions routes
In the v4 programming model, you can organize your routes in several ways. You could leave the route definition with the route handler in a single file. This is fine for an application with one endpoint. As a developer at Tailwind Traders, you know this application will grow to many APIs which need to be organized.
To start that organization, create a new
./functions/src/index.ts
file to capture the route definitions.Add the dependency for the app provided from the
@azure/functions
package.import { app } from '@azure/functions';
Add the dependency for the getVacations function from the
./functions/getVacations
file.import { getVacations } from `./functions/getVacations`;
Move the route definition from
./functions/getVacations
to the index.ts file. Update the method property array toGET
.app.http('getVacations', { methods: ['GET'], route: 'vacations', authLevel: 'anonymous', handler: getVacations });
Naming the function and handler
The name getVacations
is used as both the first parameter to app.http and as a property in the second parameter. This may be confusing and you may want different naming rules in your organization or team, depending on how the name is used.
- First parameter - name as string: The value for the first parameter is the name of the function as it will appear in the Azure portal. Those names are listed alphanumerically in the portal, so you may want to use a naming convention that groups similar functions together by purpose, such as
vacationGet
or by method, such asgetVacation
. You may also choose a different case such as snake_case, kebab-case, or camelCase. - Second parameter - handler function: The value for the second parameter is the name of the function handler as it is imported and used in the code. This name should be descriptive and match the purpose of the function. It can conform to naming conventions you already have for functions in your code base and may be enforced with typical code conformity tools.
Create the remaining functions
There are four endpoints in the Node.js Express application, and you just created the function for the GET
endpoint. Now create functions for the remaining route endpoints.
Method | HTTP trigger name | Route |
---|---|---|
POST |
postVacation |
vacations |
PUT |
updateVacation |
vacations/{id} |
DELETE |
deleteVacation |
vacations/{id} |
While the GET and POST routes are the same. The PUT
and DELETE
routes use a parameter to identify which vacation to use.
Create the HTTP POST function
Create the POST
function that handles adding a vacation.
In Visual Studio Code, open the command palette with Ctrl + Shift +P and type
Azure Functions: Create Function
and press Enter.Select HTTP Trigger as the type, and postVacation as the name.
Add the vacationService import statement to the file.
import { vacationService } from '../services';
Replace the boilerplate
postVacation
function with the following code for data access and error handling.export async function postVacation(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`HTTP function processed request for URL: "${request.url}"`); try { const vacation = await request.json() as Vacation; // Validate the vacation object if (!vacation || typeof vacation !== 'object' || !vacation.name || !vacation.description) { return { status: 400, jsonBody: { error: 'Invalid or missing vacation data.' } }; } // Data access logic const newVacation = vacationService.addVacation(vacation); // Successfully added the vacation return { status: 201, jsonBody: newVacation }; } catch (error: unknown) { const err = error as Error; context.error(`Error create vacation: ${err.message}`); return { status: 500, jsonBody: { error: 'Failed to create vacation' } }; } }
To read the incoming vacation data, you use the
request.json()
method. This method returns a promise that resolves to the JSON data in the request body. You then use theawait
keyword to wait for the promise to resolve. Theas Vacation
syntax is a type assertion that tells TypeScript to treat the result as aVacation
object.const vacation = await request.json() as Vacation;
Move the route definition from the postVacation file to the index.ts file. Update the method property array to
POST
.app.http('post-vacation', { methods: ['POST'], route: 'vacations', authLevel: 'anonymous', handler: postVacation });
Create the HTTP PUT function
Create the PUT
function that handles adding a vacation.
In Visual Studio Code, open the command palette with Ctrl + Shift + P and type
Azure Functions: Create Function
and press Enter.Select HTTP Trigger as the type, and updateVacation as the name.
Add the vacationService import statement to the file.
import { vacationService } from '../services';
Replace the boilerplate
updateVacation
function with the following code for data access and error handling.export async function updateVacation(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { try { const id = request.params.id; const { name, description } = await request.json() as Vacation; // Data access logic const updatedVacation = vacationService.updateVacation({ id, name, description }); if (updatedVacation !== undefined) { return { status: 200, jsonBody: { updatedVacation } }; } else { return { status: 404, jsonBody: { error: `Vacation with ID ${id} not found` } }; } } catch (error: unknown) { const err = error as Error; context.error(`Error updating vacation: ${err.message}`); return { status: 500, jsonBody: { error: 'Failed to update vacation' } }; } };
The
request.params.id
property is used to get the vacation ID from the URL. Therequest.json()
method is used to get the vacation data from the request body. Theas Vacation
syntax is a type assertion that tells TypeScript to treat the result as aVacation
object.Move the route definition from the putVacation file to the index.ts file. Update the method property array to
PUT
.app.http('updateVacation', { methods: ['PUT'], route: 'vacations/{id}', authLevel: 'anonymous', handler: updateVacation });
Create the HTTP DELETE function
Create the DELETE
function that handles adding a vacation.
In Visual Studio Code, open the command palette with Ctrl + Shift + P and type
Azure Functions: Create Function
and press Enter.Select HTTP Trigger as the type, and deleteVacation as the name.
Add the vacationService import to the file.
import { vacationService } from '../services';
Replace the boilerplate
deleteVacation
function with the following code for data access and error handling.export async function deleteVacation(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function processed request for url "${request.url}"`); try { const id = request.params.id; if (!id) { return { status: 400, jsonBody: { error: 'ID parameter is required' } }; } const deletedVacation = vacationService.deleteVacation(id); if (deletedVacation) { return { status: 204, jsonBody: { deleteVacation } }; } else { return { status: 404, jsonBody: { error: `Vacation with ID ${id} not found` } }; } } catch (error: unknown) { const err = error as Error; context.error(`Error deleting vacation: ${err.message}`); return { status: 500, jsonBody: { error: 'Failed to delete vacation' } }; } };
The
request.params.id
property is used to get the vacation ID from the URL.Move the route definition from the deleteVacation file to the index.ts file. Update the method property array to
DELETE
.app.http('deleteVacation', { methods: ['DELETE'], route: 'vacations/{id}', authLevel: 'anonymous', handler: deleteVacation });
Go to the next unit to review the Azure Functions application you created.