{"id":16405576,"url":"https://github.com/thames-technology/apigen","last_synced_at":"2025-03-21T03:30:36.486Z","repository":{"id":59046236,"uuid":"532292095","full_name":"thames-technology/apigen","owner":"thames-technology","description":"Generate standard Protobuf and ts-rest APIs following best-practice design patterns","archived":false,"fork":false,"pushed_at":"2024-05-26T12:53:25.000Z","size":182,"stargazers_count":15,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-18T09:21:11.766Z","etag":null,"topics":["api","api-design","buf","cli","go","golang","grpc","protobuf"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thames-technology.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"patreon":"thames"}},"created_at":"2022-09-03T15:01:06.000Z","updated_at":"2024-08-15T07:41:17.000Z","dependencies_parsed_at":"2024-05-28T10:58:49.865Z","dependency_job_id":"f31f7c13-ccb2-4b0f-b93f-3e5813f0d936","html_url":"https://github.com/thames-technology/apigen","commit_stats":null,"previous_names":["thames-technology/apigen","slavovojacek/cloudstd"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thames-technology%2Fapigen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thames-technology%2Fapigen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thames-technology%2Fapigen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thames-technology%2Fapigen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thames-technology","download_url":"https://codeload.github.com/thames-technology/apigen/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221811376,"owners_count":16884305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-design","buf","cli","go","golang","grpc","protobuf"],"created_at":"2024-10-11T06:06:32.334Z","updated_at":"2024-10-28T09:13:25.943Z","avatar_url":"https://github.com/thames-technology.png","language":"Go","funding_links":["https://patreon.com/thames"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/thames-technology/apigen/main/.github/assets/apigen-cover.png\" alt=\"API Gen Logo\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"right\"\u003e\n  \u003ci\u003eIf you use this repo, star it ✨\u003c/i\u003e\n\u003c/p\u003e\n\n---\n\n\u003ch2 align=\"center\"\u003eGenerate standard Protobuf and ts-rest APIs following best-practice design patterns\u003c/h2\u003e\n\n\u003cp align=\"center\"\u003e\n  Inspired by \u003ca href=\"https://www.oreilly.com/library/view/api-design-patterns/9781617295850/\" target=\"_blank\"\u003eAPI Design Patterns\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Install\n\nHomebrew:\n\n```sh\nbrew install thames-technology/tap/apigen\n```\n\nGo:\n\n```sh\ngo install github.com/thames-technology/apigen\n```\n\n## Getting started\n\n### Protobuf\n\nCreate `BookService` with `author` parent resource:\n\n```sh\napigen proto -r book -p author -pkg bookservice.v1alpha1 -w\n```\n\nThis will generate the following standard API definitions in `proto/bookservice/v1alpha1/service.proto`:\n\n```proto\nsyntax = \"proto3\";\n\npackage bookservice.v1alpha1;\n\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/field_mask.proto\";\nimport \"google/protobuf/timestamp.proto\";\n\n// =============================================================================\n// Service definition\n// =============================================================================\n\n// The BookService defines the standard methods for managing books.\nservice BookService {\n  // Creates a new book in the library.\n  rpc CreateBook(CreateBookRequest) returns (CreateBookResponse);\n  // Retrieves a book by its ID.\n  rpc GetBook(GetBookRequest) returns (GetBookResponse);\n  // Lists books with pagination support.\n  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);\n  // Updates an existing book.\n  rpc UpdateBook(UpdateBookRequest) returns (UpdateBookResponse);\n  // Deletes a book by its ID.\n  rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty);\n}\n\n// =============================================================================\n// Service request and response messages\n// =============================================================================\n\n// Request message for CreateBook method.\nmessage CreateBookRequest {\n  // UUID used to prevent duplicate requests.\n  optional string request_id = 1;\n  // The book to be created.\n  Book book = 2;\n}\n\n// Response message for CreateBook method.\nmessage CreateBookResponse {\n  // The created book.\n  Book book = 1;\n}\n\n// Request message for GetBook method.\nmessage GetBookRequest {\n  // ID of the book to retrieve.\n  string book_id = 1;\n}\n\n// Response message for GetBook method.\nmessage GetBookResponse {\n  // The retrieved book.\n  Book book = 1;\n}\n\n// Request message for ListBooks method.\nmessage ListBooksRequest {\n  // The maximum number of books to return.\n  optional int32 page_size = 1;\n  // The token to retrieve the next page of results.\n  optional string page_token = 2;\n  // ID of the author to list books for.\n  optional string author_id = 3;\n}\n\n// Response message for ListBooks method.\nmessage ListBooksResponse {\n  // The list of books.\n  repeated Book results = 1;\n  // Token to retrieve the next page of results.\n  string next_page_token = 2;\n  // Estimated total number of results.\n  int32 total_results_estimate = 3;\n}\n\n// Request message for UpdateBook method.\nmessage UpdateBookRequest {\n  // UUID used to prevent duplicate requests.\n  optional string request_id = 1;\n  // ID of the book to update.\n  string book_id = 2;\n  // The book to be updated.\n  Book book = 3;\n  // The field mask specifying the fields to update.\n  google.protobuf.FieldMask update_mask = 4;\n}\n\n// Response message for UpdateBook method.\nmessage UpdateBookResponse {\n  // The updated book.\n  Book book = 1;\n}\n\n// Request message for DeleteBook method.\nmessage DeleteBookRequest {\n  // ID of the book to delete.\n  string id = 1;\n}\n\n// =============================================================================\n// Resource messages\n// =============================================================================\n\n// The Book message represents a book in the library.\nmessage Book {\n  // Unique identifier for the book.\n  string id = 1;\n  // When the book was created.\n  google.protobuf.Timestamp create_time = 2;\n  // When the book was last updated.\n  google.protobuf.Timestamp update_time = 3;\n  // ID of the author of the book.\n  string author_id = 4;\n  // Additional fields for the book.\n  // TODO: Add fields here.\n}\n```\n\nCreate `AuthorService` definition without a parent resource:\n\n```sh\napigen proto -r author -pkg authorservice.v1alpha1 -w\n```\n\nThis will generate the following standard API definitions in `proto/authorservice/v1alpha1/service.proto`:\n\n```proto\nsyntax = \"proto3\";\n\npackage authorservice.v1alpha1;\n\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/field_mask.proto\";\nimport \"google/protobuf/timestamp.proto\";\n\n// =============================================================================\n// Service definition\n// =============================================================================\n\n// The AuthorService defines the standard methods for managing authors.\nservice AuthorService {\n  // Creates a new author in the library.\n  rpc CreateAuthor(CreateAuthorRequest) returns (CreateAuthorResponse);\n  // Retrieves a author by its ID.\n  rpc GetAuthor(GetAuthorRequest) returns (GetAuthorResponse);\n  // Lists authors with pagination support.\n  rpc ListAuthors(ListAuthorsRequest) returns (ListAuthorsResponse);\n  // Updates an existing author.\n  rpc UpdateAuthor(UpdateAuthorRequest) returns (UpdateAuthorResponse);\n  // Deletes a author by its ID.\n  rpc DeleteAuthor(DeleteAuthorRequest) returns (google.protobuf.Empty);\n}\n\n// =============================================================================\n// Service request and response messages\n// =============================================================================\n\n// Request message for CreateAuthor method.\nmessage CreateAuthorRequest {\n  // UUID used to prevent duplicate requests.\n  optional string request_id = 1;\n  // The author to be created.\n  Author author = 2;\n}\n\n// Response message for CreateAuthor method.\nmessage CreateAuthorResponse {\n  // The created author.\n  Author author = 1;\n}\n\n// Request message for GetAuthor method.\nmessage GetAuthorRequest {\n  // ID of the author to retrieve.\n  string author_id = 1;\n}\n\n// Response message for GetAuthor method.\nmessage GetAuthorResponse {\n  // The retrieved author.\n  Author author = 1;\n}\n\n// Request message for ListAuthors method.\nmessage ListAuthorsRequest {\n  // The maximum number of authors to return.\n  optional int32 page_size = 1;\n  // The token to retrieve the next page of results.\n  optional string page_token = 2;\n}\n\n// Response message for ListAuthors method.\nmessage ListAuthorsResponse {\n  // The list of authors.\n  repeated Author results = 1;\n  // Token to retrieve the next page of results.\n  string next_page_token = 2;\n  // Estimated total number of results.\n  int32 total_results_estimate = 3;\n}\n\n// Request message for UpdateAuthor method.\nmessage UpdateAuthorRequest {\n  // UUID used to prevent duplicate requests.\n  optional string request_id = 1;\n  // ID of the author to update.\n  string author_id = 2;\n  // The author to be updated.\n  Author author = 3;\n  // The field mask specifying the fields to update.\n  google.protobuf.FieldMask update_mask = 4;\n}\n\n// Response message for UpdateAuthor method.\nmessage UpdateAuthorResponse {\n  // The updated author.\n  Author author = 1;\n}\n\n// Request message for DeleteAuthor method.\nmessage DeleteAuthorRequest {\n  // ID of the author to delete.\n  string id = 1;\n}\n\n// =============================================================================\n// Resource messages\n// =============================================================================\n\n// The Author message represents a author in the library.\nmessage Author {\n  // Unique identifier for the author.\n  string id = 1;\n  // When the author was created.\n  google.protobuf.Timestamp create_time = 2;\n  // When the author was last updated.\n  google.protobuf.Timestamp update_time = 3;\n  // Additional fields for the author.\n  // TODO: Add fields here.\n}\n```\n\n### TypeScript with ts-rest\n\n\u003e [!NOTE]\n\u003e Learn more about ts-rest: \u003chttps://ts-rest.com/\u003e\n\nCreate `book` contract with `author` parent resource:\n\n```sh\napigen ts-rest -r book -p author -w\n```\n\nThis will generate the following standard API definitions in `contracts/bookContract.ts`:\n\n```ts\nimport { initContract } from '@ts-rest/core';\nimport { z } from 'zod';\n\n// ====================================================================================================================\n// Schemas\n// ====================================================================================================================\n\n// Timestamps using ISO 8601 format\nconst Timestamp = z.string().datetime().describe('ISO 8601 format timestamp');\n\n// Book schema\nexport const Book = z.object({\n  id: z.string().ulid().describe('Unique identifier for the book'),\n  createTime: Timestamp.describe('When the book was created'),\n  updateTime: Timestamp.describe('When the book was last updated'),\n  authorId: z.string().ulid().describe('ID of the author of the book'),\n});\n\n// ====================================================================================================================\n// Request and response schemas\n// ====================================================================================================================\n\nexport const CreateBookRequest = z.object({\n  requestId: z.string().uuid().optional().describe('UUID used to prevent duplicate requests'),\n  book: Book.omit({ id: true, createTime: true, updateTime: true }),\n});\n\nexport const CreateBookResponse = z.object({\n  book: Book,\n});\n\nexport const GetBookRequest = z.object({\n  bookId: z.string().ulid().describe('The unique identifier of the book'),\n});\n\nexport const GetBookResponse = z.object({\n  book: Book,\n});\n\nexport const ListBooksRequest = z.object({\n  pageSize: z.number().int().min(1).max(1000).default(100).describe('The maximum number of books to return'),\n  pageToken: z.string().optional().describe('The token to retrieve the next page of results'),\n  authorId: z.string().ulid().optional().describe('The unique identifier of the parent author resource'),\n});\n\nexport const ListBooksResponse = z.object({\n  results: z.array(Book).describe('The list of books'),\n  nextPageToken: z.string().ulid().nullish().describe('The token to retrieve the next page of items'),\n  totalResultsEstimate: z.number().int().min(0).describe('The estimated total number of results'),\n});\n\nexport const UpdateBookRequest = z.object({\n  requestId: z.string().uuid().optional().describe('UUID used to prevent duplicate requests'),\n  bookId: z.string().ulid().describe('The unique identifier of the book to update'),\n  book: Book.omit({ id: true, createTime: true, updateTime: true }).partial(),\n});\n\nexport const UpdateBookResponse = z.object({\n  book: Book,\n});\n\nexport const DeleteBookRequest = z.object({\n  bookId: z.string().ulid().describe('The unique identifier of the book to delete'),\n});\n\nexport const DeleteBookResponse = z.null();\n\n// ====================================================================================================================\n// Contracts\n// ====================================================================================================================\n\nconst c = initContract();\n\nexport const BookServiceContract = c.router({\n  createBook: {\n    method: 'POST',\n    path: '/books',\n    body: CreateBookRequest,\n    responses: {\n      201: CreateBookResponse,\n    },\n    summary: 'Create a book',\n  },\n  getBook: {\n    method: 'GET',\n    path: '/books/:bookId',\n    pathParams: GetBookRequest,\n    responses: {\n      200: GetBookResponse,\n    },\n    summary: 'Get a book by ID',\n  },\n  listBooks: {\n    method: 'GET',\n    path: '/books',\n    query: ListBooksRequest,\n    responses: {\n      200: ListBooksResponse,\n    },\n    summary: 'List all books',\n  },\n  updateBook: {\n    method: 'PATCH',\n    path: '/books/:bookId',\n    pathParams: UpdateBookRequest.pick({ bookId: true }),\n    body: UpdateBookRequest.omit({ bookId: true }),\n    responses: {\n      200: UpdateBookResponse,\n    },\n    summary: 'Update a book',\n  },\n  deleteBook: {\n    method: 'DELETE',\n    path: '/books/:bookId',\n    pathParams: DeleteBookRequest,\n    body: z.null(), // No body, but we need to specify it as null\n    responses: {\n      204: DeleteBookResponse,\n    },\n    summary: 'Delete a book',\n  },\n});\n```\n\nCreate `author` contract withouth a parent resource and using uuid as id:\n\n```sh\napigen ts-rest -r author --id uuid -w\n```\n\nThis will generate the following standard API definitions in `contracts/authorContract.ts`:\n\n```ts\nimport { initContract } from '@ts-rest/core';\nimport { z } from 'zod';\n\n// ====================================================================================================================\n// Schemas\n// ====================================================================================================================\n\n// Timestamps using ISO 8601 format\nconst Timestamp = z.string().datetime().describe('ISO 8601 format timestamp');\n\n// Author schema\nexport const Author = z.object({\n  id: z.string().uuid().describe('Unique identifier for the author'),\n  createTime: Timestamp.describe('When the author was created'),\n  updateTime: Timestamp.describe('When the author was last updated'),\n});\n\n// ====================================================================================================================\n// Request and response schemas\n// ====================================================================================================================\n\nexport const CreateAuthorRequest = z.object({\n  requestId: z.string().uuid().optional().describe('UUID used to prevent duplicate requests'),\n  author: Author.omit({ id: true, createTime: true, updateTime: true }),\n});\n\nexport const CreateAuthorResponse = z.object({\n  author: Author,\n});\n\nexport const GetAuthorRequest = z.object({\n  authorId: z.string().uuid().describe('The unique identifier of the author'),\n});\n\nexport const GetAuthorResponse = z.object({\n  author: Author,\n});\n\nexport const ListAuthorsRequest = z.object({\n  pageSize: z.number().int().min(1).max(1000).default(100).describe('The maximum number of authors to return'),\n  pageToken: z.string().optional().describe('The token to retrieve the next page of results'),\n});\n\nexport const ListAuthorsResponse = z.object({\n  results: z.array(Author).describe('The list of authors'),\n  nextPageToken: z.string().uuid().nullish().describe('The token to retrieve the next page of items'),\n  totalResultsEstimate: z.number().int().min(0).describe('The estimated total number of results'),\n});\n\nexport const UpdateAuthorRequest = z.object({\n  requestId: z.string().uuid().optional().describe('UUID used to prevent duplicate requests'),\n  authorId: z.string().uuid().describe('The unique identifier of the author to update'),\n  author: Author.omit({ id: true, createTime: true, updateTime: true }).partial(),\n});\n\nexport const UpdateAuthorResponse = z.object({\n  author: Author,\n});\n\nexport const DeleteAuthorRequest = z.object({\n  authorId: z.string().uuid().describe('The unique identifier of the author to delete'),\n});\n\nexport const DeleteAuthorResponse = z.null();\n\n// ====================================================================================================================\n// Contracts\n// ====================================================================================================================\n\nconst c = initContract();\n\nexport const AuthorServiceContract = c.router({\n  createAuthor: {\n    method: 'POST',\n    path: '/authors',\n    body: CreateAuthorRequest,\n    responses: {\n      201: CreateAuthorResponse,\n    },\n    summary: 'Create a author',\n  },\n  getAuthor: {\n    method: 'GET',\n    path: '/authors/:authorId',\n    pathParams: GetAuthorRequest,\n    responses: {\n      200: GetAuthorResponse,\n    },\n    summary: 'Get a author by ID',\n  },\n  listAuthors: {\n    method: 'GET',\n    path: '/authors',\n    query: ListAuthorsRequest,\n    responses: {\n      200: ListAuthorsResponse,\n    },\n    summary: 'List all authors',\n  },\n  updateAuthor: {\n    method: 'PATCH',\n    path: '/authors/:authorId',\n    pathParams: UpdateAuthorRequest.pick({ authorId: true }),\n    body: UpdateAuthorRequest.omit({ authorId: true }),\n    responses: {\n      200: UpdateAuthorResponse,\n    },\n    summary: 'Update a author',\n  },\n  deleteAuthor: {\n    method: 'DELETE',\n    path: '/authors/:authorId',\n    pathParams: DeleteAuthorRequest,\n    body: z.null(), // No body, but we need to specify it as null\n    responses: {\n      204: DeleteAuthorResponse,\n    },\n    summary: 'Delete a author',\n  },\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthames-technology%2Fapigen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthames-technology%2Fapigen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthames-technology%2Fapigen/lists"}