{"id":296,"date":"2026-06-18T23:34:51","date_gmt":"2026-06-19T04:34:51","guid":{"rendered":"https:\/\/jeffsantillan.com\/blog\/?p=296"},"modified":"2026-06-19T00:20:08","modified_gmt":"2026-06-19T05:20:08","slug":"nestjs-uso-de-swagger-para-documentacion","status":"publish","type":"post","link":"https:\/\/jeffsantillan.com\/blog\/nestjs-uso-de-swagger-para-documentacion\/","title":{"rendered":"Nestjs uso de Swagger para documentaci\u00f3n"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Swagger es una herramienta que genera documentaci\u00f3n interactiva de tu API autom\u00e1ticamente. En lugar de documentar manualmente cada endpoint en un Word o Notion, Swagger lee tu c\u00f3digo y genera una interfaz web donde cualquier developer puede ver y probar la API sin usar Postman o PowerShell<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Extendiendo el proyecto de Usuario: <a href=\"https:\/\/jeffsantillan.com\/blog\/nestjs-uso-de-validator-y-transformers\/\" data-type=\"link\" data-id=\"https:\/\/jeffsantillan.com\/blog\/nestjs-uso-de-validator-y-transformers\/\">Enlace<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Podemos incluirle el Swagger para documentar la API<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">instalar<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install @nestjs\/swagger<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Es asi que al archivo de main.ts le voy a incluir o habilitar Swagger, hay que importar en el main y agregar las siguientes lineas<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>import { DocumentBuilder, SwaggerModule } from '@nestjs\/swagger';\n\n\/\/ Swagger\n  const config = new DocumentBuilder()\n      .setTitle('Sistema de Usuario API')\n      .setDescription('API para gestion de usuarios')\n      .setVersion('1.0')\n      .addTag('usuario')\n      .build();\n\n  const document = SwaggerModule.createDocument(app, config);\n  SwaggerModule.setup('api', app, document);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Quedando el archivo main.ts de la siguiente manera.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { NestFactory } from '@nestjs\/core';\nimport { AppModule } from '.\/app.module';\nimport { ValidationPipe } from '@nestjs\/common'; \/\/ Validator\nimport { DocumentBuilder, SwaggerModule } from '@nestjs\/swagger';\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n\n  app.useGlobalPipes(new ValidationPipe({\n    whitelist: true, \/\/ Elimina los campos que no estan en los DTOs\n    forbidNonWhitelisted: true, \/\/ Lanza un error si llegan campos extra\n    transform: true,  \/\/ Transforma tipos automaticamente.\n  }));\n\n  \/\/ Swagger\n  const config = new DocumentBuilder()\n      .setTitle('Sistema de Usuario API')\n      .setDescription('API para gestion de usuarios')\n      .setVersion('1.0')\n      .addTag('usuario')\n      .build();\n\n  const document = SwaggerModule.createDocument(app, config);\n  SwaggerModule.setup('api', app, document);\n\n  await app.listen(process.env.PORT ?? 3000);\n}\nbootstrap();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luego hay que decorar los DTOs, importando el ApiProperty y utilizar el decorador en cada campo con la estructura de datos con example y description.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { IsEmail, IsNotEmpty, IsString, IsNumber, Min, Max } from 'class-validator';\nimport { Type } from 'class-transformer';\n<strong>import { ApiProperty } from '@nestjs\/swagger';<\/strong>\n\nexport class CreateUsuarioDto {\n\n    <strong>@ApiProperty({ example: 'Jefferson', description: 'Nombre del usuario'})<\/strong>\n    @IsNotEmpty({message: 'El nombre es obligatorio'})\n    @IsString()\n    nombre!: string;\n\n    <strong>@ApiProperty({ example: 'jeffsp@gmail.com', description: 'Correo del usuario'})<\/strong>\n    @IsNotEmpty()\n    @IsEmail({}, {message: 'Email inv\u00e1lido'})\n    email!: string;\n\n    <strong>@ApiProperty({ example: '30', description: 'Edad del usuario'})<\/strong>\n    @IsNumber()\n    @Min(18, {message: 'Debe de ser mayor de 18'})\n    @Max(99)\n    @Type( () =&gt; Number) \/\/Aqui se utiliza la clase transform explicitamente.\n    edad!: number;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finalmente, mejorar el controlador con: ApiTags, ApiOperation y ApiResponse<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs\/common';\nimport { UsuarioService } from '.\/usuario.service';\nimport { CreateUsuarioDto } from '.\/dto\/create-usuario.dto';\nimport { UpdateUsuarioDto } from '.\/dto\/update-usuario.dto';\n<strong>import { ApiTags, ApiOperation, ApiResponse } from '@nestjs\/swagger';<\/strong>\n\n<em><strong>@ApiTags('usuario')<\/strong><\/em>\n@Controller('usuario')\nexport class UsuarioController {\n  constructor(private readonly usuarioService: UsuarioService) {}\n\n  <strong><em>@ApiOperation({summary:'Crear nuevo usuario'})\n  @ApiResponse({status:201, description:'Usuario creado'})\n  @ApiResponse({status:400, description:'Datos inv\u00e1lidos'})<\/em><\/strong>\n  @Post()\n  create(@Body() createUsuarioDto: CreateUsuarioDto) {\n    return this.usuarioService.create(createUsuarioDto);\n  }\n\n  @ApiOperation({summary: 'Listar todos los usuarios'})\n  @ApiResponse({status:200, description:'Lista de usuarios'})\n  @Get()\n  findAll() {\n    return this.usuarioService.findAll();\n  }\n\n  @ApiOperation({summary: 'Obtener usuario por ID'})\n  @ApiResponse({status:200, description: 'Usuario encontrado'})\n  @ApiResponse({status:404, description: 'Usuario no encontrado'})\n  @Get(':id')\n  findOne(@Param('id') id: string) {\n    return this.usuarioService.findOne(+id);\n  }\n\n  @ApiOperation({summary: 'Actualizar usuario'})\n  @ApiResponse({status: 200, description: 'Usuario actualizado'})\n  @Patch(':id')\n  update(@Param('id') id: string, @Body() updateUsuarioDto: UpdateUsuarioDto) {\n    return this.usuarioService.update(+id, updateUsuarioDto);\n  }\n\n  @ApiOperation({summary: 'Eliminar usuario'})\n  @ApiResponse({status: 200, description: 'Usuario Eliminado'})\n  @Delete(':id')\n  remove(@Param('id') id: string) {\n    return this.usuarioService.remove(+id);\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Descripci\u00f3n de decoradores:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>@ApiTags(&#8216;usuario&#8217;) = Agrupa endpoints bajo una secci\u00f3n en la UI<\/li>\n\n\n\n<li>@ApiProperty() = Documenta cada campo del DTO con ejemplo<\/li>\n\n\n\n<li>@ApiOperation() = Describe qu\u00e9 hace el endpoint<\/li>\n\n\n\n<li>@ApiResponse() = Documenta los posibles c\u00f3digos de respuesta<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Ello seria suficiente para tener una API documentada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez configurado, accede a <strong><code>http:\/\/localhost:3000\/api<\/code> <\/strong>para ver la documentaci\u00f3n interactiva.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"882\" height=\"885\" src=\"https:\/\/jeffsantillan.com\/blog\/wp-content\/uploads\/2026\/06\/image-47.png\" alt=\"\" class=\"wp-image-297\" srcset=\"https:\/\/jeffsantillan.com\/blog\/wp-content\/uploads\/2026\/06\/image-47.png 882w, https:\/\/jeffsantillan.com\/blog\/wp-content\/uploads\/2026\/06\/image-47-300x300.png 300w, https:\/\/jeffsantillan.com\/blog\/wp-content\/uploads\/2026\/06\/image-47-150x150.png 150w, https:\/\/jeffsantillan.com\/blog\/wp-content\/uploads\/2026\/06\/image-47-768x771.png 768w\" sizes=\"auto, (max-width: 882px) 100vw, 882px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">\u00a1Saludos!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Swagger es una herramienta que genera documentaci\u00f3n interactiva de tu API autom\u00e1ticamente. En lugar de documentar &hellip; <a title=\"Nestjs uso de Swagger para documentaci\u00f3n\" class=\"hm-read-more\" href=\"https:\/\/jeffsantillan.com\/blog\/nestjs-uso-de-swagger-para-documentacion\/\"><span class=\"screen-reader-text\">Nestjs uso de Swagger para documentaci\u00f3n<\/span>Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":2,"featured_media":211,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[48,2],"tags":[53,49,55],"class_list":["post-296","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-nestjs","category-programacion","tag-api","tag-nestjs","tag-swagger"],"_links":{"self":[{"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/posts\/296","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/comments?post=296"}],"version-history":[{"count":3,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/posts\/296\/revisions"}],"predecessor-version":[{"id":307,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/posts\/296\/revisions\/307"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/media\/211"}],"wp:attachment":[{"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/media?parent=296"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/categories?post=296"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jeffsantillan.com\/blog\/wp-json\/wp\/v2\/tags?post=296"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}