Generando SDKs¶
Como FastAPI está basado en la especificación OpenAPI, sus APIs se pueden describir en un formato estándar que muchas herramientas entienden.
Esto facilita generar documentación actualizada, paquetes de cliente (SDKs) en múltiples lenguajes y escribir pruebas o flujos de automatización que se mantengan sincronizados con tu código.
En esta guía, aprenderás a generar un SDK de TypeScript para tu backend con FastAPI.
Generadores de SDKs de código abierto¶
Una opción versátil es el OpenAPI Generator, que soporta muchos lenguajes de programación y puede generar SDKs a partir de tu especificación OpenAPI.
Para clientes de TypeScript, Hey API es una solución diseñada específicamente, que ofrece una experiencia optimizada para el ecosistema de TypeScript.
Puedes descubrir más generadores de SDK en OpenAPI.Tools.
Consejo
FastAPI genera automáticamente especificaciones OpenAPI 3.1, así que cualquier herramienta que uses debe soportar esta versión.
Generadores de SDKs de sponsors de FastAPI¶
Esta sección destaca soluciones respaldadas por empresas y venture-backed de compañías que sponsorean FastAPI. Estos productos ofrecen funcionalidades adicionales e integraciones además de SDKs generados de alta calidad.
Al ✨ sponsorear FastAPI ✨, estas compañías ayudan a asegurar que el framework y su ecosistema se mantengan saludables y sustentables.
Su sponsorship también demuestra un fuerte compromiso con la comunidad de FastAPI (tú), mostrando que no solo les importa ofrecer un gran servicio, sino también apoyar un framework robusto y próspero, FastAPI. 🙇
Por ejemplo, podrías querer probar:
Algunas de estas soluciones también pueden ser open source u ofrecer niveles gratuitos, así que puedes probarlas sin un compromiso financiero. Hay otros generadores de SDK comerciales disponibles y se pueden encontrar en línea. 🤓
Crea un SDK de TypeScript¶
Empecemos con una aplicación simple de FastAPI:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
return {"message": "item received"}
@app.get("/items/", response_model=list[Item])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
🤓 Other versions and variants
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
return {"message": "item received"}
@app.get("/items/", response_model=List[Item])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
Nota que las path operations definen los modelos que usan para el payload del request y el payload del response, usando los modelos Item y ResponseMessage.
Documentación de la API¶
Si vas a /docs, verás que tiene los esquemas para los datos a enviar en requests y recibir en responses:

Puedes ver esos esquemas porque fueron declarados con los modelos en la app.
Esa información está disponible en el OpenAPI schema de la app, y luego se muestra en la documentación de la API.
Y esa misma información de los modelos que está incluida en OpenAPI es lo que puede usarse para generar el código del cliente.
Hey API¶
Una vez que tenemos una app de FastAPI con los modelos, podemos usar Hey API para generar un cliente de TypeScript. La forma más rápida de hacerlo es con npx.
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
Esto generará un SDK de TypeScript en ./src/client.
Puedes aprender cómo instalar @hey-api/openapi-ts y leer sobre el output generado en su sitio web.
Usar el SDK¶
Ahora puedes importar y usar el código del cliente. Podría verse así, nota que tienes autocompletado para los métodos:

También obtendrás autocompletado para el payload a enviar:

Consejo
Nota el autocompletado para name y price, que fue definido en la aplicación de FastAPI, en el modelo Item.
Tendrás errores en línea para los datos que envíes:

El objeto de response también tendrá autocompletado:

App de FastAPI con tags¶
En muchos casos tu app de FastAPI será más grande, y probablemente usarás tags para separar diferentes grupos de path operations.
Por ejemplo, podrías tener una sección para items y otra sección para users, y podrían estar separadas por tags:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
class User(BaseModel):
username: str
email: str
@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
return {"message": "Item received"}
@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
return {"message": "User received"}
🤓 Other versions and variants
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
class User(BaseModel):
username: str
email: str
@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
return {"message": "Item received"}
@app.get("/items/", response_model=List[Item], tags=["items"])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
return {"message": "User received"}
Genera un Cliente TypeScript con tags¶
Si generas un cliente para una app de FastAPI usando tags, normalmente también separará el código del cliente basándose en los tags.
De esta manera podrás tener las cosas ordenadas y agrupadas correctamente para el código del cliente:

En este caso tienes:
ItemsServiceUsersService
Nombres de los métodos del cliente¶
Ahora mismo los nombres de los métodos generados como createItemItemsPost no se ven muy limpios:
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
...eso es porque el generador del cliente usa el operation ID interno de OpenAPI para cada path operation.
OpenAPI requiere que cada operation ID sea único a través de todas las path operations, por lo que FastAPI usa el nombre de la función, el path, y el método/operación HTTP para generar ese operation ID, porque de esa manera puede asegurarse de que los operation IDs sean únicos.
Pero te mostraré cómo mejorar eso a continuación. 🤓
Operation IDs personalizados y mejores nombres de métodos¶
Puedes modificar la forma en que estos operation IDs son generados para hacerlos más simples y tener nombres de métodos más simples en los clientes.
En este caso tendrás que asegurarte de que cada operation ID sea único de alguna otra manera.
Por ejemplo, podrías asegurarte de que cada path operation tenga un tag, y luego generar el operation ID basado en el tag y el name de la path operation (el nombre de la función).
Función personalizada para generar ID único¶
FastAPI usa un ID único para cada path operation, se usa para el operation ID y también para los nombres de cualquier modelo personalizado necesario, para requests o responses.
Puedes personalizar esa función. Toma un APIRoute y retorna un string.
Por ejemplo, aquí está usando el primer tag (probablemente tendrás solo un tag) y el nombre de la path operation (el nombre de la función).
Puedes entonces pasar esa función personalizada a FastAPI como el parámetro generate_unique_id_function:
from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel
def custom_generate_unique_id(route: APIRoute):
return f"{route.tags[0]}-{route.name}"
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
class User(BaseModel):
username: str
email: str
@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
return {"message": "Item received"}
@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
return {"message": "User received"}
🤓 Other versions and variants
from typing import List
from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel
def custom_generate_unique_id(route: APIRoute):
return f"{route.tags[0]}-{route.name}"
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
class User(BaseModel):
username: str
email: str
@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
return {"message": "Item received"}
@app.get("/items/", response_model=List[Item], tags=["items"])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
return {"message": "User received"}
Genera un Cliente TypeScript con operation IDs personalizados¶
Ahora, si generas el cliente de nuevo, verás que tiene los nombres de métodos mejorados:

Como ves, los nombres de métodos ahora tienen el tag y luego el nombre de la función, ahora no incluyen información del path de la URL y la operación HTTP.
Preprocesa la especificación OpenAPI para el generador de clientes¶
El código generado aún tiene algo de información duplicada.
Ya sabemos que este método está relacionado con los items porque esa palabra está en el ItemsService (tomado del tag), pero aún tenemos el nombre del tag prefijado en el nombre del método también. 😕
Probablemente aún querremos mantenerlo para OpenAPI en general, ya que eso asegurará que los operation IDs sean únicos.
Pero para el cliente generado podríamos modificar los operation IDs de OpenAPI justo antes de generar los clientes, solo para hacer esos nombres de métodos más bonitos y limpios.
Podríamos descargar el JSON de OpenAPI a un archivo openapi.json y luego podríamos remover ese tag prefijado con un script como este:
import json
from pathlib import Path
file_path = Path("./openapi.json")
openapi_content = json.loads(file_path.read_text())
for path_data in openapi_content["paths"].values():
for operation in path_data.values():
tag = operation["tags"][0]
operation_id = operation["operationId"]
to_remove = f"{tag}-"
new_operation_id = operation_id[len(to_remove) :]
operation["operationId"] = new_operation_id
file_path.write_text(json.dumps(openapi_content))
import * as fs from 'fs'
async function modifyOpenAPIFile(filePath) {
try {
const data = await fs.promises.readFile(filePath)
const openapiContent = JSON.parse(data)
const paths = openapiContent.paths
for (const pathKey of Object.keys(paths)) {
const pathData = paths[pathKey]
for (const method of Object.keys(pathData)) {
const operation = pathData[method]
if (operation.tags && operation.tags.length > 0) {
const tag = operation.tags[0]
const operationId = operation.operationId
const toRemove = `${tag}-`
if (operationId.startsWith(toRemove)) {
const newOperationId = operationId.substring(toRemove.length)
operation.operationId = newOperationId
}
}
}
}
await fs.promises.writeFile(
filePath,
JSON.stringify(openapiContent, null, 2),
)
console.log('File successfully modified')
} catch (err) {
console.error('Error:', err)
}
}
const filePath = './openapi.json'
modifyOpenAPIFile(filePath)
Con eso, los operation IDs serían renombrados de cosas como items-get_items a solo get_items, de esa manera el generador del cliente puede generar nombres de métodos más simples.
Genera un Cliente TypeScript con el OpenAPI preprocesado¶
Como el resultado final ahora está en un archivo openapi.json, necesitas actualizar la ubicación de la entrada:
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
Después de generar el nuevo cliente, ahora tendrías nombres de métodos limpios, con todo el autocompletado, errores en línea, etc:

Beneficios¶
Cuando uses los clientes generados automáticamente obtendrás autocompletado para:
- Métodos.
- Payloads de request en el body, parámetros de query, etc.
- Payloads de response.
También tendrás errores en línea para todo.
Y cada vez que actualices el código del backend, y regeneres el frontend, tendrás las nuevas path operations disponibles como métodos, las antiguas eliminadas, y cualquier otro cambio se reflejará en el código generado. 🤓
Esto también significa que si algo cambió será reflejado automáticamente en el código del cliente. Y si haces build del cliente, dará error si tienes algún desajuste en los datos utilizados.
Así que, detectarás muchos errores muy temprano en el ciclo de desarrollo en lugar de tener que esperar a que los errores se muestren a tus usuarios finales en producción para luego intentar depurar dónde está el problema. ✨