Skip to main content

Add a new serverless contract

Serverless contracts are a central part of Swarmion. They let you define interactions between your services to enable more stability and type safety. You can learn more in the dedicated documentation section

In this section, we will create a contract library for the users backend, and how to use it.

Prerequisites

For this section, we will suppose that there is an existing frontend service in the monorepo application:

Swarmion app with frontendSwarmion app with frontend
info

The generate-frontend-service command is a planned development by the Swarmion core team. In the meantime, you can use the Swarmion Full Stack template that has a built-in frontend.

First, lets generate the library, with the generator we used in the previous section:

pnpm generate-library users-contracts --directory contracts
info

The --directory option let you chose the path containing your generated library. Default value is packages

Then, we will add this contract library as a dependency for the users service and the frontend:

cd services/users
pnpm add @my-project-name/users-contracts

cd ../../frontend
pnpm add @my-project-name/users-contracts

The project dependency graph should now looks like the following:

Swarmion app with users contracts libSwarmion app with users contracts lib

At last, you should reference the contracts library tsconfig.build.json file in both contract consumers in order to have live type updates without the need to package the contracts library.

services/users/tsconfig.json
{
"compilerOptions": {
// ...
"references": [{ "path": "../../contracts/users-contracts" }]
}
// ...
}
frontend/tsconfig.json
{
// ...
"compilerOptions": {
// ...
"references": [{ "path": "../contracts/users-contracts" }]
}
// ...
}

Define our first contract

Now lets write our first contract to define a get user operation.

contracts/users-contracts/src/contracts/getUser.ts
import {
ApiGatewayContract,
HttpStatusCodes,
} from '@swarmion/serverless-contracts';

const pathParametersSchema = {
type: 'object',
properties: {
userId: { type: 'string' },
},
required: ['userId'],
additionalProperties: false,
} as const;

const userEntitySchema = {
type: 'object',
properties: {
userId: { type: 'string' },
userName: { type: 'string' },
email: { type: 'string' },
},
required: ['userId', 'userName', 'email'],
additionalProperties: false,
} as const;

const getUserContract = new ApiGatewayContract({
id: 'users-getUser',
path: '/users/{userId}',
method: 'GET',
integrationType: 'httpApi',
pathParametersSchema,
outputSchemas: {
[HttpStatusCodes.OK]: userEntitySchema,
},
});

export default getUserContract;
caution

If you are using @swarmion/serverless-contracts@0.17.0 or an older version, you will have to define every property even if they are undefined:

const getUserContract = new ApiGatewayContract({
id: 'users-getUser',
path: '/users/{userId}',
method: 'GET',
integrationType: 'httpApi',
authorizerType: undefined,
pathParametersSchema,
queryStringParametersSchema: undefined,
headersSchema: undefined,
bodySchema: undefined,
outputSchema: userEntitySchema,
});

The ability to omit undefined properties was added in 0.18.0.

There is a lot of things to see here

Schemas

pathParametersSchema and userEntitySchema are JSON schemas that define the shape of the data that will be consumed and produced by the contract. You can learn more about JSON schemas here.

Contract definition

Then, the getUserContract completely defines the contract, from the API path and method, the expected input and output, to the implementation details such as integration type or authorizer type.

Exporting the contract

In order to use your newly created contract, you will need to export it from the contracts/users-contracts/src/index.ts file:

contracts/users-contracts/src/index.ts
export { default as getUserContract } from './contracts/getUser';

Using contract features

Now that we have defined our first contract, we can use it in our services. First, we will be able to define the trigger of our function with the getTrigger feature:

services/users/src/functions/getUser/config.ts
import { getTrigger } from '@swarmion/serverless-contracts';
import { getHandlerPath, LambdaFunction } from '@swarmion/serverless-helpers';

import { getUserContract } from '@my-project-name/users-contracts';

const config: LambdaFunction = {
environment: {},
handler: getHandlerPath(__dirname),
events: [getTrigger(getUserContract)],
};

export default config;

This will create an http-api endpoint that will trigger the getUser function when a request is made to the /users/{userId} path.

Then, you can use the getHandler feature to define a type safe handler of your function:

services/users/src/functions/getUser/handler.ts
import { getHandler, HttpStatusCodes } from '@swarmion/serverless-contracts';
import { getUserContract } from '@my-project-name/users-contracts';
import Ajv from 'ajv';

const ajv = new Ajv();

export const handler = getHandler(
getUserContract,
ajv,
)(async event => {
const { userId } = event.pathParameters; // userId will be typed as string

console.log(userId);

await Promise.resolve(); // you should replace this by your business logic

return {
statusCode: HttpStatusCodes.OK,
body: {
userId,
userName: 'John Doe',
email: 'john.doe@swarmion.com',
},
}; // The return type is checked against the contract output schemas
});

Finally, in your frontend service, you can use the getFetchRequest feature to create a type safe client for your contract:

frontend/src/services/getUser.ts
import {
getFetchRequest,
HttpStatusCodes,
} from '@swarmion/serverless-contracts';
import { getUserContract } from '@my-project-name/users-contracts';

export const getUser = (userId: string) => {
const { statusCode, body } = getFetchRequest(getUserContract, fetch, {
pathParameters: {
userId,
},
});

if (statusCode !== HttpStatusCodes.OK) {
// manage error cases
}

return body;
};

Wrap up

In this tutorial, we have seen how to use the @swarmion/serverless-contracts package to define and use contracts in our services.

If you want to learn more about serverless contracts, you can read this documentation.

You should now be able to create a full stack application using Swarmion 🎉.