Snøkam logo Tilbake

Nysnø: Genererte API-klienter med OpenAPI - Slik gjør du livet ditt enklere som utvikler 😎

Er du lei av å bruke tid på å forstå API-er, finne ut hvordan de fungerer, hvilke data som må sendes inn, og hva du får tilbake? Er du lei av å manuelt tolke responser og skrive modeller/DTO-er som ofte ender opp med feil? 🤯

Da har jeg gode nyheter til deg! 🎉 Ved å bruke OpenAPI-standarden kan utviklere spesifisere hvordan et API fungerer og automatisk generere API-klienter som andre apper kan bruke. Dette gjør integrasjon med systemer til en drøm 💭 Og sånn har vi nå gjort det med alle Snøkam sine API-er.

Men hvordan fungerer det egentlig?

OpenAPI-spesifikasjon: Dette er hjertet av prosessen. Spesifikasjonen beskriver nøyaktig hvordan et API skal brukes — hvilke endepunkter som finnes, hvilke data som kan sendes, og hvordan responsene ser ut. Tenk på det som en detaljert bruksanvisning for API-et ditt, skrevet i et format som både mennesker og maskiner forstår.

Verktøy for API-generering: Med verktøy som Swagger Codegen eller OpenAPI Generator kan du automatisk generere en API-klient for en rekke programmeringsspråk basert på OpenAPI-spesifikasjonen. Uansett om du jobber i JavaScript, Python, Java, eller noe helt annet, kan du få en ferdigbygd klient skreddersydd for ditt språk. I Snøkam genererer vi klienter for TypeScript og C#, men kan enkelt utvide og inkludere andre språk.

Integrasjon i prosjektet: Når klienten er generert, installerer du den i prosjektet ditt som en hvilken som helst annen modul eller bibliotek. Dette gjør det utrolig enkelt å integrere API-et i koden din. Pretty sweet, ikke sant?

Fordeler med genererte API-klienter

Konsistens: Klientene er generert direkte fra API-ets spesifikasjon, noe som sikrer nøyaktighet og eliminerer misforståelser om hvordan API-et fungerer.

Hastighet: Du slipper å skrive klientkoden selv, noe som sparer tid — tid du kan bruke på å fokusere på mer kritiske oppgaver eller ta en ekstra kaffepause ☕

Pålitelighet: Automatiseringen reduserer risikoen for feil, siden klientene genereres direkte fra spesifikasjonen.

Enklere vedlikehold: Når API-et endres, er det lett å oppdatere klientene på tvers av apper.

Et eksempel fra Snøkam 🚀

Vi har nylig utviklet et API i Snøkam som betaler ut daglig lønn i Bitcoin 💰 Det kan du for øvrig lese mer om i denne posten på Linkedin. For å få det til å fungere, har vi laget et API med flere endepunkter, blant annet et som henter ut alle transaksjoner.

[Function("GetTransactions")]
[OpenApiOperation("GetTransactions", "Transaction")]
[OpenApiParameter("from", In = ParameterLocation.Query, Type = typeof(DateTime), Description = "From datetime")]
[OpenApiParameter("to", In = ParameterLocation.Query, Type = typeof(DateTime), Description = "To datetime")]
[OpenApiResponseWithBody(HttpStatusCode.OK, "application/json", typeof(List<Transaction>), Description = "List of transactions")]
[OpenApiSecurity("Implicit", SecuritySchemeType.OAuth2, Flows = typeof(ImplicitAuthFlow))]
public async Task<IActionResult> GetTransactions(
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "v1.0/transactions")] HttpRequest req,
DateTime? from,
DateTime? to)
{
var transactions = await coinbaseService.GetTransactions(from, to);
return new OkObjectResult(transactions);
}

Denne koden spesifiserer nøyaktig hvordan endepunktet fungerer, hvilke parametere det aksepterer, og hvilke data det returnerer. Denne informasjonen brukes til å generere OpenAPI-spesifikasjonen, som igjen lar oss automatisk generere klienter.

Når vi dytter kode-endringer til main, starter en workflow i GitHub Actions som genererer nye klienter basert på versjonering med Semantic Release.

- name: Generate Typescript client
uses: openapi-generators/openapitools-generator-action@v1
with:
generator: typescript-fetch
openapi-url: ${{ inputs.openApiUrl }}
command-args: |
--additional-properties=npmName=${{ inputs.packageName }},npmVersion=${{ inputs.version }},supportsES6=true \
--openapi-normalizer SET_TAGS_FOR_ALL_OPERATIONS=${{ inputs.clientName }} \
--api-name-suffix=Client \
--skip-validate-spec

Resultatet? To nye klienter:

Men hvordan ser det ut på klientsiden?

🚀 I C# kan en integrasjon se slik ut:

using Microsoft.Extensions.Logging;
using Snokam.Client.Crypto.Api;
using Snokam.Client.Crypto.Client;
using Snokam.Client.Crypto.Client.Auth;
using Snokam.Client.Crypto.Model;
namespace Snokam.PowerOfficeFunction.Services;
public class CryptoService
{
private readonly ICryptoClientAsync _cryptoClient;
private ILogger<CryptoService> _logger;
public CryptoService(ILogger<CryptoService> logger)
{
_logger = logger;
_cryptoClient = new CryptoClient(
new Configuration
{
BasePath = BASE_URL,
OAuthClientId = CLIENT_ID,
OAuthClientSecret = CLIENT_SECRET,
OAuthTokenUrl = $"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
OAuthFlow = OAuthFlow.APPLICATION,
OAuthScope = scope
}
);
}
public async Task<TransactionSummary> GetCryptoSummary(string email, DateTime from, DateTime to)
{
return await _cryptoClient.GetTransactionsSummaryAsync(email, from, to);
}
public async Task<List<EmployeeTransactionSummary>> GetCryptoSummaries(DateTime from, DateTime to)
{
return await _cryptoClient.GetTransactionsSummariesAsync(from, to);
}
}

_cryptoClient vil inkludere funksjoner for alle de tilsvarende endepunktene på API-et, og vil hente dataen med oppgitt autentisering.

I Typescript har vi valgt å gjøre det litt mer komplisert. Har du hørt om React Query fra TanStack? 😊 React Query er et populært bibliotek for React-applikasjoner som forenkler håndtering av server-state. Det gir utviklere enkle hooks for å hente, cache, synkronisere og oppdatere data fra API-er på en enkel måte.

Med litt magi gjør vi om alle funksjonene i @snokam/crypto-client om til React Query hooks 🤯 😀

import { Configuration, CryptoClient } from "@snokam/crypto-client";
import { useClientQuery } from "@snokam/react-query";
export const useCryptoClient = () => {
const { isAuthenticated, getToken } = useAuth();
const configuration = new Configuration({
basePath: `${process.env.NEXT_PUBLIC_CRYPTO_API_BASE_URL}`,
...(isAuthenticated && {
accessToken: () =>
getToken([`${process.env.NEXT_PUBLIC_CRYPTO_API_SCOPE}`]),
}),
});
return useMemo(() => new CryptoClient(configuration), [configuration]);
};
export const useCryptoQuery = () =>
useClientQuery({
name: "Crypto",
client: useCryptoClient(),
});

Og voila, plutselig har vi tilgjengelig hooks for hvert endepunkt i API-et vårt, som er en lek å bruke 🤓

const { useGetMyTransactionsSummaryQuery } = useCryptoQuery();
const {
data: cryptoSummary,
isRefetching: cryptoSummaryRefetching,
isLoading: cryptoSummaryLoading,
error: cryptoSummaryError,
} = useGetMyTransactionsSummaryQuery(
{ from: startOfMonth(new Date()), to: endOfMonth(new Date()) },
{ refetchOnWindowFocus: true },
);

Happy coding! 🎉🚀