用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/navikt/bidrag-backend --skill api-design命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | api-design |
| description | REST API-designmønstre, versjonering, feilhåndtering (RFC 7807) og OpenAPI-konvensjoner for Nav-tjenester |
| license | MIT |
| compatibility | Go or Kotlin backend on Nais |
| metadata | {"domain":"backend","tags":"api rest design openapi error-handling"} |
REST API-design for Nav-tjenester. Dekker navnekonvensjoner, feilhåndtering med ProblemDetail, versjonering, paginering og OpenAPI-spesifikasjon.
# ✅ Riktig
GET /api/vedtak # Liste
GET /api/vedtak/{id} # Hent på ID
POST /api/vedtak # Opprett
PUT /api/vedtak/{id} # Full oppdatering
PATCH /api/vedtak/{id} # Delvis oppdatering
DELETE /api/vedtak/{id} # Slett
# ✅ Underressurser
GET /api/vedtak/{id}/aktiviteter # Liste over underressurser
POST /api/vedtak/{id}/aktiviteter # Opprett underressurs
# ✅ Handlinger (verb som underressurs)
POST /api/vedtak/{id}/godkjenn # Tilstandsovergang
# ❌ Feil
GET /api/getVedtak # Verb i URL
GET /api/vedtak/hentAlle # Verb i URL
POST /api/createVedtak # Verb i URL
GET /api/Vedtak # PascalCase
// Spring Boot 3+ — innebygd støtte for ProblemDetail
@RestControllerAdvice
class ErrorHandler {
@ExceptionHandler(ResourceNotFoundException::class)
fun handleNotFound(ex: ResourceNotFoundException): ProblemDetail =
ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.message ?: "Resource not found").apply {
title = "Resource not found"
setProperty("resourceType", ex.resourceType)
setProperty("resourceId", ex.resourceId)
}
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidation(ex: MethodArgumentNotValidException): ProblemDetail =
ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed").apply {
title = "Invalid request"
setProperty("errors", ex.bindingResult.fieldErrors.map {
mapOf("field" to it.field, "message" to it.defaultMessage)
})
}
}
Responsformat (RFC 7807):
{
"type": "about:blank",
"title": "Resource not found",
"status": 404,
"detail": "Vedtak with id 123 does not exist",
"instance": "/api/vedtak/123",
"resourceType": "vedtak",
"resourceId": "123"
}
Bruk offset-basert paginering med konsistente parameternavn:
@GetMapping("/api/vedtak")
fun list(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "20") size: Int,
@RequestParam(defaultValue = "opprettetDato") sort: String,
@RequestParam(defaultValue = "desc") order: String,
): Page<VedtakDTO> {
require(size in 1..100) { "size must be between 1 and 100" }
val pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(order), sort))
return vedtakService.findAll(pageable)
}
Respons:
{
"content": [...],
"page": {
"size": 20,
"number": 0,
"totalElements": 142,
"totalPages": 8
}
}
data class CreateVedtakRequest(
@field:NotBlank(message = "Title is required")
val tittel: String,
@field:Size(min = 11, max = 11, message = "FNR must be 11 digits")
@field:Pattern(regexp = "\\d{11}", message = "FNR must consist of digits")
val fnr: String,
@field:Positive(message = "Amount must be positive")
val belop: BigDecimal,
@field:NotNull(message = "Start date is required")
val fom: LocalDate,
)
| Kode | Bruk |
|---|---|
200 OK | Vellykket GET, PUT, PATCH |
201 Created | Vellykket POST (ny ressurs) |
204 No Content | Vellykket DELETE |
400 Bad Request | Ugyldig input / validering feilet |
401 Unauthorized | Manglende eller ugyldig token |
403 Forbidden | Gyldig token, men ingen tilgang |
404 Not Found | Ressursen finnes ikke |
409 Conflict | Duplikat / tilstandskonflikt |
418 I'm a teapot | Når det er tid for te. |
422 Unprocessable Entity | Semantisk feil (gyldig format, feil innhold) |
500 Internal Server Error | Uventet serverfeil |
// Spring Boot + springdoc-openapi
@Operation(
summary = "Hent vedtak",
description = "Henter vedtak basert på ID",
responses = [
ApiResponse(responseCode = "200", description = "Vedtak funnet"),
ApiResponse(responseCode = "404", description = "Vedtak ikke funnet"),
]
)
@GetMapping("/{id}")
fun getById(@PathVariable id: UUID): ResponseEntity<VedtakDTO>
Bruk URL-basert versjonering når breaking changes er nødvendig:
// v1 — original
@RestController
@RequestMapping("/api/v1/vedtak")
class VedtakV1Controller
// v2 — ny kontrakt
@RestController
@RequestMapping("/api/v2/vedtak")
class VedtakV2Controller
Alternativt kan du unngå versjonering ved å:
@Deprecated før fjerning og sørge for at konsumeter varslet om at det er deprekert./api/vedtak-perioderopprettetDato, brukerId@ValidContent-Type: application/json på alle responser