[{"data":1,"prerenderedAt":38},["ShallowReactive",2],{"4584059":3},{"type_of":4,"id":5,"title":6,"description":7,"readable_publish_date":8,"slug":9,"path":10,"url":11,"comments_count":12,"public_reactions_count":12,"collection_id":13,"published_timestamp":14,"language":15,"subforem_id":16,"ai_disclosure_level":17,"ai_disclosure_label":18,"positive_reactions_count":12,"cover_image":13,"social_image":19,"canonical_url":11,"created_at":20,"edited_at":21,"crossposted_at":13,"published_at":14,"last_comment_at":14,"reading_time_minutes":22,"tag_list":23,"tags":24,"body_html":29,"body_markdown":30,"user":31},"article",4584059,"How close can explicit HTTP endpoints get to server-function DX?","Next.js, SolidStart, TanStack Start, and SvelteKit all offer some form of server or remote functions....","Sep 5","how-close-can-explicit-http-endpoints-get-to-server-function-dx-5b6c","/yoshinoriishii/how-close-can-explicit-http-endpoints-get-to-server-function-dx-5b6c","https://dev.to/yoshinoriishii/how-close-can-explicit-http-endpoints-get-to-server-function-dx-5b6c",0,null,"2026-09-05T19:55:43Z","en",1,"not_disclosed","Not Disclosed","https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Five7pilkp4syxo6aab32.png","2026-09-05T19:27:41Z","2026-09-06T17:40:47Z",5,"nuxt, typescript, vue, webdev",[25,26,27,28],"nuxt","typescript","vue","webdev","\u003Cp>Next.js, SolidStart, TanStack Start, and SvelteKit all offer some form of server or remote functions. The integrations differ, but being able to connect server code to forms and data fetching is appealing.\u003C/p>\n\n\u003Cp>I've used Nuxt since its early releases, and I like Nuxt 5's work on typed HTTP routes. Seeing those approaches develop made me wonder:\u003C/p>\n\n\u003Cblockquote>\n\u003Cp>How much of that DX can we get while keeping an explicit HTTP endpoint as the starting point?\u003C/p>\n\u003C/blockquote>\n\n\u003Cp>I'm trying this in \u003Ca href=\"https://github.com/nuxt-endpoints/nuxt-endpoints\" target=\"_blank\" rel=\"noopener noreferrer\">Nuxt Endpoints\u003C/a>. Routes keep their files, URLs, and HTTP methods. NE's \u003Ccode>defineRouteHandler()\u003C/code> builds on the validated-routing design being discussed in \u003Ca href=\"https://github.com/h3js/h3/issues/1437\" target=\"_blank\" rel=\"noopener noreferrer\">H3 RFC #1437\u003C/a>: request and response schemas alongside the handler, with a contract that other tools can read. NE implements and extends that design to connect it to client behavior, including pagination and forms. The upstream design is still under discussion; these examples use NE's implementation.\u003C/p>\n\n\u003Cp>The closest existing comparison is oRPC, which already combines type-safe procedures, explicit OpenAPI routing, and Pinia Colada integration across frameworks. Nuxt Endpoints explores a more Nuxt-native trade-off: keeping file-based routes as the source of truth, avoiding a parallel procedure router, and deriving behavior such as cursor pagination directly from semantic HTTP contracts.\u003C/p>\n\n\u003Cp>The idea is that a server contract should do more than type the response. It should constrain the handler and let the client use features that depend on that contract. A pagination adapter, for instance, should require a route that declares pagination.\u003C/p>\n\n\u003Ch2>\n  \u003Ca name=\"pagination-what-the-declaration-buys-you\" href=\"#pagination-what-the-declaration-buys-you\">\n  \u003C/a>\n  Pagination: what the declaration buys you\n\u003C/h2>\n\n\u003Cp>Cursor pagination is a small example of the idea:\u003Cbr>\n\u003C/p>\n\n\u003Cdiv class=\"highlight js-code-highlight\">\n\u003Cpre class=\"highlight typescript\">\u003Ccode>\u003Cspan class=\"c1\">// server/api/articles.get.ts\u003C/span>\n\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">z\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">zod\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\n\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">defineRouteHandler\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">nuxt-endpoints/runtime\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\n\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">listArticles\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">../utils/articles\u003C/span>\u003Cspan class=\"dl\">'\u003C/span> \u003Cspan class=\"c1\">// your database code\u003C/span>\n\n\u003Cspan class=\"kd\">const\u003C/span> \u003Cspan class=\"nx\">Article\u003C/span> \u003Cspan class=\"o\">=\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">object\u003C/span>\u003Cspan class=\"p\">({\u003C/span>\n  \u003Cspan class=\"na\">id\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">number\u003C/span>\u003Cspan class=\"p\">(),\u003C/span>\n  \u003Cspan class=\"na\">title\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">string\u003C/span>\u003Cspan class=\"p\">(),\u003C/span>\n\u003Cspan class=\"p\">})\u003C/span>\n\n\u003Cspan class=\"k\">export\u003C/span> \u003Cspan class=\"k\">default\u003C/span> \u003Cspan class=\"nf\">defineRouteHandler\u003C/span>\u003Cspan class=\"p\">({\u003C/span>\n  \u003Cspan class=\"na\">pagination\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"na\">kind\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">cursor\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span> \u003Cspan class=\"na\">item\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">Article\u003C/span> \u003Cspan class=\"p\">},\u003C/span>\n  \u003Cspan class=\"na\">handler\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"nx\">event\u003C/span>\u003Cspan class=\"p\">)\u003C/span> \u003Cspan class=\"o\">=&gt;\u003C/span> \u003Cspan class=\"nf\">listArticles\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"nx\">event\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">validated\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">query\u003C/span>\u003Cspan class=\"p\">),\u003C/span>\n\u003Cspan class=\"p\">})\u003C/span>\n\u003C/code>\u003C/pre>\n\u003Cdiv class=\"highlight__panel js-actions-panel\">\n\u003Cdiv class=\"highlight__panel-action js-fullscreen-code-action\">\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\">\u003Ctitle>Enter fullscreen mode\u003C/title>\n    \u003Cpath d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\">\u003C/path>\n\u003C/svg>\n\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\">\u003Ctitle>Exit fullscreen mode\u003C/title>\n    \u003Cpath d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\">\u003C/path>\n\u003C/svg>\n\n\u003C/div>\n\u003C/div>\n\u003C/div>\n\n\n\n\u003Cp>This defines \u003Ccode>GET /api/articles\u003C/code>. The database query and cursor encoding are your code. NE supplies the rest of the pagination contract:\u003C/p>\n\n\u003Cul>\n\u003Cli>the request has validated \u003Ccode>cursor\u003C/code> and \u003Ccode>limit\u003C/code> query fields;\u003C/li>\n\u003Cli>the \u003Ccode>200\u003C/code> response is \u003Ccode>{ items: Article[], nextCursor?: string }\u003C/code>;\u003C/li>\n\u003Cli>the handler's successful return must match that shape;\u003C/li>\n\u003Cli>the generated OpenAPI document exposes those fields;\u003C/li>\n\u003Cli>the client can use an adapter that requires this contract.\u003C/li>\n\u003C/ul>\n\n\u003Cp>Returning \u003Ccode>{ nextCursor }\u003C/code> without \u003Ccode>items\u003C/code>, for example, is a server-side type error. This page format is NE's convention, not an HTTP standard. The contract checks the shape; it cannot prove that your database query returns the right next page.\u003C/p>\n\n\u003Cp>The client can pass the same typed request to Pinia Colada:\u003Cbr>\n\u003C/p>\n\n\u003Cdiv class=\"highlight js-code-highlight\">\n\u003Cpre class=\"highlight typescript\">\u003Ccode>\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">useInfiniteQuery\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">@pinia/colada\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\n\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">infiniteQueryOptions\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">#endpoints/colada\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\n\n\u003Cspan class=\"kd\">const\u003C/span> \u003Cspan class=\"nx\">articles\u003C/span> \u003Cspan class=\"o\">=\u003C/span> \u003Cspan class=\"nf\">useInfiniteQuery\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\n  \u003Cspan class=\"nf\">infiniteQueryOptions\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\n    \u003Cspan class=\"nf\">$endpoint\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">/api/articles\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"na\">method\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">get\u003C/span>\u003Cspan class=\"dl\">'\u003C/span> \u003Cspan class=\"p\">}),\u003C/span>\n  \u003Cspan class=\"p\">),\u003C/span>\n\u003Cspan class=\"p\">)\u003C/span>\n\u003C/code>\u003C/pre>\n\u003Cdiv class=\"highlight__panel js-actions-panel\">\n\u003Cdiv class=\"highlight__panel-action js-fullscreen-code-action\">\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\">\u003Ctitle>Enter fullscreen mode\u003C/title>\n    \u003Cpath d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\">\u003C/path>\n\u003C/svg>\n\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\">\u003Ctitle>Exit fullscreen mode\u003C/title>\n    \u003Cpath d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\">\u003C/path>\n\u003C/svg>\n\n\u003C/div>\n\u003C/div>\n\u003C/div>\n\n\n\n\u003Cp>\u003Ccode>infiniteQueryOptions()\u003C/code> rejects an endpoint without the cursor-pagination contract at compile time. Nuxt Endpoints connects \u003Ccode>nextCursor\u003C/code> to the next HTTP request; Pinia Colada owns the page cache, reactive state, and refetch lifecycle.\u003C/p>\n\n\u003Ch2>\n  \u003Ca name=\"handle-responses-by-status\" href=\"#handle-responses-by-status\">\n  \u003C/a>\n  Handle responses by status\n\u003C/h2>\n\n\u003Cp>For a detail route declaring \u003Ccode>200\u003C/code> with an article and \u003Ccode>404\u003C/code> with a message, checking the status narrows the body type:\u003Cbr>\n\u003C/p>\n\n\u003Cdiv class=\"highlight js-code-highlight\">\n\u003Cpre class=\"highlight typescript\">\u003Ccode>\u003Cspan class=\"kd\">const\u003C/span> \u003Cspan class=\"nx\">result\u003C/span> \u003Cspan class=\"o\">=\u003C/span> \u003Cspan class=\"k\">await\u003C/span> \u003Cspan class=\"nf\">$endpoint\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">/api/articles/:id\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n  \u003Cspan class=\"na\">method\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">get\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n  \u003Cspan class=\"na\">params\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"na\">id\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">42\u003C/span>\u003Cspan class=\"dl\">'\u003C/span> \u003Cspan class=\"p\">},\u003C/span>\n\u003Cspan class=\"p\">})\u003C/span>\n\n\u003Cspan class=\"k\">if \u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"nx\">result\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">status\u003C/span> \u003Cspan class=\"o\">===\u003C/span> \u003Cspan class=\"mi\">200\u003C/span>\u003Cspan class=\"p\">)\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n  \u003Cspan class=\"nx\">result\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">body\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">title\u003C/span>\n\u003Cspan class=\"p\">}\u003C/span>\n\n\u003Cspan class=\"k\">if \u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"nx\">result\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">status\u003C/span> \u003Cspan class=\"o\">===\u003C/span> \u003Cspan class=\"mi\">404\u003C/span>\u003Cspan class=\"p\">)\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n  \u003Cspan class=\"nx\">result\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">body\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">message\u003C/span>\n\u003Cspan class=\"p\">}\u003C/span>\n\u003C/code>\u003C/pre>\n\u003Cdiv class=\"highlight__panel js-actions-panel\">\n\u003Cdiv class=\"highlight__panel-action js-fullscreen-code-action\">\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\">\u003Ctitle>Enter fullscreen mode\u003C/title>\n    \u003Cpath d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\">\u003C/path>\n\u003C/svg>\n\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\">\u003Ctitle>Exit fullscreen mode\u003C/title>\n    \u003Cpath d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\">\u003C/path>\n\u003C/svg>\n\n\u003C/div>\n\u003C/div>\n\u003C/div>\n\n\n\n\u003Cp>The \u003Ccode>404\u003C/code> is a result you can handle, not a thrown fetch error. Network failures still reject the request. These types describe the declared responses; they cannot account for every response a proxy or other infrastructure might return.\u003C/p>\n\n\u003Cp>The same route is callable from another service or with \u003Ccode>curl\u003C/code>. Its HTTP interface is documented in the generated OpenAPI, so callers don't need NE to use it.\u003C/p>\n\n\u003Ch2>\n  \u003Ca name=\"forms-from-the-same-contract\" href=\"#forms-from-the-same-contract\">\n  \u003C/a>\n  Forms, from the same contract\n\u003C/h2>\n\n\u003Cp>The experimental Nuxt 5 prototype applies the same idea to progressively enhanced forms. The server is still an explicit POST route:\u003Cbr>\n\u003C/p>\n\n\u003Cdiv class=\"highlight js-code-highlight\">\n\u003Cpre class=\"highlight typescript\">\u003Ccode>\u003Cspan class=\"c1\">// server/api/users.post.ts\u003C/span>\n\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">z\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">zod\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\n\u003Cspan class=\"k\">import\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"nx\">defineRouteHandler\u003C/span>\u003Cspan class=\"p\">,\u003C/span> \u003Cspan class=\"nx\">formOf\u003C/span> \u003Cspan class=\"p\">}\u003C/span> \u003Cspan class=\"k\">from\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">nuxt-endpoints/runtime\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\n\n\u003Cspan class=\"kd\">const\u003C/span> \u003Cspan class=\"nx\">UserInput\u003C/span> \u003Cspan class=\"o\">=\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">object\u003C/span>\u003Cspan class=\"p\">({\u003C/span>\n  \u003Cspan class=\"na\">name\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">string\u003C/span>\u003Cspan class=\"p\">().\u003C/span>\u003Cspan class=\"nf\">min\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"mi\">1\u003C/span>\u003Cspan class=\"p\">),\u003C/span>\n\u003Cspan class=\"p\">})\u003C/span>\n\n\u003Cspan class=\"k\">export\u003C/span> \u003Cspan class=\"k\">default\u003C/span> \u003Cspan class=\"nf\">defineRouteHandler\u003C/span>\u003Cspan class=\"p\">({\u003C/span>\n  \u003Cspan class=\"na\">form\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n    \u003Cspan class=\"na\">action\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">/users/new\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n    \u003Cspan class=\"na\">redirect\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">/users/{id}\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n  \u003Cspan class=\"p\">},\u003C/span>\n  \u003Cspan class=\"na\">validate\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n    \u003Cspan class=\"na\">body\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n      \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">application/json\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">UserInput\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n      \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">application/x-www-form-urlencoded\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nf\">formOf\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"nx\">UserInput\u003C/span>\u003Cspan class=\"p\">),\u003C/span>\n    \u003Cspan class=\"p\">},\u003C/span>\n    \u003Cspan class=\"na\">response\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n      \u003Cspan class=\"mi\">201\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">object\u003C/span>\u003Cspan class=\"p\">({\u003C/span> \u003Cspan class=\"na\">id\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">number\u003C/span>\u003Cspan class=\"p\">(),\u003C/span> \u003Cspan class=\"na\">name\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">z\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">string\u003C/span>\u003Cspan class=\"p\">()\u003C/span> \u003Cspan class=\"p\">}),\u003C/span>\n    \u003Cspan class=\"p\">},\u003C/span>\n  \u003Cspan class=\"p\">},\u003C/span>\n  \u003Cspan class=\"na\">handler\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"nx\">event\u003C/span>\u003Cspan class=\"p\">)\u003C/span> \u003Cspan class=\"o\">=&gt;\u003C/span>\n    \u003Cspan class=\"nx\">event\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nf\">respond\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"mi\">201\u003C/span>\u003Cspan class=\"p\">,\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n      \u003Cspan class=\"na\">id\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"mi\">1\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n      \u003Cspan class=\"na\">name\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"nx\">event\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">validated\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">body\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">name\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n    \u003Cspan class=\"p\">}),\u003C/span>\n\u003Cspan class=\"p\">})\u003C/span>\n\u003C/code>\u003C/pre>\n\u003Cdiv class=\"highlight__panel js-actions-panel\">\n\u003Cdiv class=\"highlight__panel-action js-fullscreen-code-action\">\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\">\u003Ctitle>Enter fullscreen mode\u003C/title>\n    \u003Cpath d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\">\u003C/path>\n\u003C/svg>\n\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\">\u003Ctitle>Exit fullscreen mode\u003C/title>\n    \u003Cpath d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\">\u003C/path>\n\u003C/svg>\n\n\u003C/div>\n\u003C/div>\n\u003C/div>\n\n\n\n\u003Cp>On the Vue page:\u003Cbr>\n\u003C/p>\n\n\u003Cdiv class=\"highlight js-code-highlight\">\n\u003Cpre class=\"highlight vue\">\u003Ccode>\u003Cspan class=\"nt\">&lt;\u003C/span>\u003Cspan class=\"k\">script\u003C/span> \u003Cspan class=\"na\">setup\u003C/span> \u003Cspan class=\"na\">lang=\u003C/span>\u003Cspan class=\"s\">\"ts\"\u003C/span>\u003Cspan class=\"nt\">&gt;\u003C/span>\n\u003Cspan class=\"kd\">const\u003C/span> \u003Cspan class=\"nx\">form\u003C/span> \u003Cspan class=\"o\">=\u003C/span> \u003Cspan class=\"nf\">useEndpointForm\u003C/span>\u003Cspan class=\"p\">(\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">/api/users\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span> \u003Cspan class=\"p\">{\u003C/span>\n  \u003Cspan class=\"na\">method\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"s1\">post\u003C/span>\u003Cspan class=\"dl\">'\u003C/span>\u003Cspan class=\"p\">,\u003C/span>\n  \u003Cspan class=\"na\">body\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"p\">{\u003C/span> \u003Cspan class=\"na\">name\u003C/span>\u003Cspan class=\"p\">:\u003C/span> \u003Cspan class=\"dl\">''\u003C/span> \u003Cspan class=\"p\">},\u003C/span>\n\u003Cspan class=\"p\">})\u003C/span>\n\u003Cspan class=\"nt\">&lt;/\u003C/span>\u003Cspan class=\"k\">script\u003C/span>\u003Cspan class=\"nt\">&gt;\u003C/span>\n\n\u003Cspan class=\"nt\">&lt;\u003C/span>\u003Cspan class=\"k\">template\u003C/span>\u003Cspan class=\"nt\">&gt;\u003C/span>\n  \u003Cspan class=\"nt\">&lt;form\u003C/span> \u003Cspan class=\"na\">v-bind=\u003C/span>\u003Cspan class=\"s\">\"form.attrs\"\u003C/span> \u003Cspan class=\"err\">@\u003C/span>\u003Cspan class=\"na\">submit=\u003C/span>\u003Cspan class=\"s\">\"form.enhance\"\u003C/span>\u003Cspan class=\"nt\">&gt;\u003C/span>\n    \u003Cspan class=\"nt\">&lt;label&gt;\u003C/span>\n      Name\n      \u003Cspan class=\"nt\">&lt;input\u003C/span> \u003Cspan class=\"na\">v-bind=\u003C/span>\u003Cspan class=\"s\">\"form.fields.name\"\u003C/span> \u003Cspan class=\"nt\">/&gt;\u003C/span>\n    \u003Cspan class=\"nt\">&lt;/label&gt;\u003C/span>\n\n    \u003Cspan class=\"nt\">&lt;p\u003C/span> \u003Cspan class=\"na\">v-for=\u003C/span>\u003Cspan class=\"s\">\"issue in form.issues.name\"\u003C/span> \u003Cspan class=\"na\">:key=\u003C/span>\u003Cspan class=\"s\">\"issue.message\"\u003C/span>\u003Cspan class=\"nt\">&gt;\u003C/span>\n      \u003Cspan class=\"si\">{{\u003C/span> \u003Cspan class=\"nx\">issue\u003C/span>\u003Cspan class=\"p\">.\u003C/span>\u003Cspan class=\"nx\">message\u003C/span> \u003Cspan class=\"si\">}}\u003C/span>\n    \u003Cspan class=\"nt\">&lt;/p&gt;\u003C/span>\n\n    \u003Cspan class=\"nt\">&lt;button&gt;\u003C/span>Create\u003Cspan class=\"nt\">&lt;/button&gt;\u003C/span>\n  \u003Cspan class=\"nt\">&lt;/form&gt;\u003C/span>\n\u003Cspan class=\"nt\">&lt;/\u003C/span>\u003Cspan class=\"k\">template\u003C/span>\u003Cspan class=\"nt\">&gt;\u003C/span>\n\u003C/code>\u003C/pre>\n\u003Cdiv class=\"highlight__panel js-actions-panel\">\n\u003Cdiv class=\"highlight__panel-action js-fullscreen-code-action\">\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\">\u003Ctitle>Enter fullscreen mode\u003C/title>\n    \u003Cpath d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\">\u003C/path>\n\u003C/svg>\n\n    \u003Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\">\u003Ctitle>Exit fullscreen mode\u003C/title>\n    \u003Cpath d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\">\u003C/path>\n\u003C/svg>\n\n\u003C/div>\n\u003C/div>\n\u003C/div>\n\n\n\n\u003Cp>\u003Ccode>useEndpointForm\u003C/code> requires a compatible form contract. It checks body values and field names against the input schema, so a field such as \u003Ccode>form.fields.email\u003C/code> would be a type error here. Build-time checks reject requirements that a native form cannot satisfy, such as a mandatory custom request header.\u003C/p>\n\n\u003Cp>NE generates the form attributes and connects server validation issues to \u003Ccode>form.issues\u003C/code>. The \u003Ca href=\"https://github.com/nuxt-endpoints/nuxt-endpoints/blob/nuxt5/docs/progressive-enhancement.md\" target=\"_blank\" rel=\"noopener noreferrer\">progressive-enhancement documentation\u003C/a> covers the generated submission route and the browser tests with and without JavaScript.\u003C/p>\n\n\u003Ch2>\n  \u003Ca name=\"what-is-available-today\" href=\"#what-is-available-today\">\n  \u003C/a>\n  What is available today?\n\u003C/h2>\n\n\u003Cp>The published Nuxt 4 version includes pagination, status-aware requests, Pinia Colada adapters, OpenAPI generation, and idempotency. These are ported into the module using its own implementation.\u003C/p>\n\n\u003Cp>The Nuxt 5 branch is where I'm experimenting with upstream integration and progressive enhancement. It requires prototype forks. The extensions in those forks have not been accepted upstream.\u003C/p>\n\n\u003Cp>I'm following Nuxt's merged \u003Ca href=\"https://github.com/nuxt/nuxt/pull/36238\" target=\"_blank\" rel=\"noopener noreferrer\">fetchdts route-typing PR\u003C/a>, which generates \u003Ccode>$fetch\u003C/code> and \u003Ccode>useFetch\u003C/code> types from reported server routes, Nitro's \u003Ca href=\"https://github.com/nitrojs/nitro/pull/4572\" target=\"_blank\" rel=\"noopener noreferrer\">removal of its previous typed-fetch implementation\u003C/a>, and the \u003Ca href=\"https://github.com/nitrojs/nitro/releases/tag/v3.0.260903-beta\" target=\"_blank\" rel=\"noopener noreferrer\">September Nitro 3 beta\u003C/a>. I'd like to contribute the contract introspection and type-inference pieces that downstream tools need, and reuse them in NE. The aim is less duplicated code while keeping NE's application APIs as stable as practical.\u003C/p>\n\n\u003Cp>You can see the current split and setup instructions on the \u003Ca href=\"https://nuxt-endpoints.github.io/nuxt-endpoints/docs/nuxt5-progress\" target=\"_blank\" rel=\"noopener noreferrer\">Nuxt 5 progress page\u003C/a>.\u003C/p>\n\n\u003Ch2>\n  \u003Ca name=\"why-not-make-every-remote-operation-a-function\" href=\"#why-not-make-every-remote-operation-a-function\">\n  \u003C/a>\n  Why not make every remote operation a function?\n\u003C/h2>\n\n\u003Cp>Server functions can expose HTTP controls, and these frameworks also support explicit API routes. I prefer starting with the route itself.\u003C/p>\n\n\u003Cp>For a simple GET, I want to write a simple GET. When I need pagination or form integration, I want to opt into that abstraction for that purpose. I can see the extra requirements in the declaration and inspect the resulting HTTP interface in OpenAPI.\u003C/p>\n\n\u003Cp>NE still introduces conventions: \u003Ccode>pagination\u003C/code> and \u003Ccode>form\u003C/code> are examples. That's fine with me as long as I can see what they add and call the endpoint without the generated client.\u003C/p>\n\n\u003Cp>These examples don't cover everything server functions offer. NE doesn't yet provide automatic cache revalidation after a mutation, for example. What I've been able to build so far makes me want to keep exploring this approach.\u003C/p>\n\n\u003Cp>What part of server-function DX would be hardest—or most valuable—to reproduce this way?\u003C/p>\n\n\u003Ch2>\n  \u003Ca name=\"links\" href=\"#links\">\n  \u003C/a>\n  Links\n\u003C/h2>\n\n\u003Cul>\n\u003Cli>\u003Ca href=\"https://nuxt-endpoints.github.io/nuxt-endpoints/\" target=\"_blank\" rel=\"noopener noreferrer\">Nuxt Endpoints documentation\u003C/a>\u003C/li>\n\u003Cli>\u003Ca href=\"https://github.com/nuxt-endpoints/nuxt-endpoints\" target=\"_blank\" rel=\"noopener noreferrer\">Nuxt Endpoints on GitHub\u003C/a>\u003C/li>\n\u003Cli>\u003Ca href=\"https://github.com/nuxt-endpoints/nuxt-endpoints/blob/main/docs/pagination-demo.md\" target=\"_blank\" rel=\"noopener noreferrer\">Cursor pagination walkthrough\u003C/a>\u003C/li>\n\u003Cli>\u003Ca href=\"https://github.com/nuxt-endpoints/nuxt-endpoints/tree/nuxt5#try-the-nuxt-5-integration-branch\" target=\"_blank\" rel=\"noopener noreferrer\">Try the Nuxt 5 integration branch\u003C/a>\u003C/li>\n\u003C/ul>\n\n","Next.js, SolidStart, TanStack Start, and SvelteKit all offer some form of server or remote functions. The integrations differ, but being able to connect server code to forms and data fetching is appealing.\n\nI've used Nuxt since its early releases, and I like Nuxt 5's work on typed HTTP routes. Seeing those approaches develop made me wonder:\n\n> How much of that DX can we get while keeping an explicit HTTP endpoint as the starting point?\n\nI'm trying this in [Nuxt Endpoints](https://github.com/nuxt-endpoints/nuxt-endpoints). Routes keep their files, URLs, and HTTP methods. NE's `defineRouteHandler()` builds on the validated-routing design being discussed in [H3 RFC #1437](https://github.com/h3js/h3/issues/1437): request and response schemas alongside the handler, with a contract that other tools can read. NE implements and extends that design to connect it to client behavior, including pagination and forms. The upstream design is still under discussion; these examples use NE's implementation.\n\nThe closest existing comparison is oRPC, which already combines type-safe procedures, explicit OpenAPI routing, and Pinia Colada integration across frameworks. Nuxt Endpoints explores a more Nuxt-native trade-off: keeping file-based routes as the source of truth, avoiding a parallel procedure router, and deriving behavior such as cursor pagination directly from semantic HTTP contracts.\n\nThe idea is that a server contract should do more than type the response. It should constrain the handler and let the client use features that depend on that contract. A pagination adapter, for instance, should require a route that declares pagination.\n\n## Pagination: what the declaration buys you\n\nCursor pagination is a small example of the idea:\n\n```ts\n// server/api/articles.get.ts\nimport { z } from 'zod'\nimport { defineRouteHandler } from 'nuxt-endpoints/runtime'\nimport { listArticles } from '../utils/articles' // your database code\n\nconst Article = z.object({\n  id: z.number(),\n  title: z.string(),\n})\n\nexport default defineRouteHandler({\n  pagination: { kind: 'cursor', item: Article },\n  handler: (event) => listArticles(event.validated.query),\n})\n```\n\nThis defines `GET /api/articles`. The database query and cursor encoding are your code. NE supplies the rest of the pagination contract:\n\n- the request has validated `cursor` and `limit` query fields;\n- the `200` response is `{ items: Article[], nextCursor?: string }`;\n- the handler's successful return must match that shape;\n- the generated OpenAPI document exposes those fields;\n- the client can use an adapter that requires this contract.\n\nReturning `{ nextCursor }` without `items`, for example, is a server-side type error. This page format is NE's convention, not an HTTP standard. The contract checks the shape; it cannot prove that your database query returns the right next page.\n\nThe client can pass the same typed request to Pinia Colada:\n\n```ts\nimport { useInfiniteQuery } from '@pinia/colada'\nimport { infiniteQueryOptions } from '#endpoints/colada'\n\nconst articles = useInfiniteQuery(\n  infiniteQueryOptions(\n    $endpoint('/api/articles', { method: 'get' }),\n  ),\n)\n```\n\n`infiniteQueryOptions()` rejects an endpoint without the cursor-pagination contract at compile time. Nuxt Endpoints connects `nextCursor` to the next HTTP request; Pinia Colada owns the page cache, reactive state, and refetch lifecycle.\n\n## Handle responses by status\n\nFor a detail route declaring `200` with an article and `404` with a message, checking the status narrows the body type:\n\n```ts\nconst result = await $endpoint('/api/articles/:id', {\n  method: 'get',\n  params: { id: '42' },\n})\n\nif (result.status === 200) {\n  result.body.title\n}\n\nif (result.status === 404) {\n  result.body.message\n}\n```\n\nThe `404` is a result you can handle, not a thrown fetch error. Network failures still reject the request. These types describe the declared responses; they cannot account for every response a proxy or other infrastructure might return.\n\nThe same route is callable from another service or with `curl`. Its HTTP interface is documented in the generated OpenAPI, so callers don't need NE to use it.\n\n## Forms, from the same contract\n\nThe experimental Nuxt 5 prototype applies the same idea to progressively enhanced forms. The server is still an explicit POST route:\n\n```ts\n// server/api/users.post.ts\nimport { z } from 'zod'\nimport { defineRouteHandler, formOf } from 'nuxt-endpoints/runtime'\n\nconst UserInput = z.object({\n  name: z.string().min(1),\n})\n\nexport default defineRouteHandler({\n  form: {\n    action: '/users/new',\n    redirect: '/users/{id}',\n  },\n  validate: {\n    body: {\n      'application/json': UserInput,\n      'application/x-www-form-urlencoded': formOf(UserInput),\n    },\n    response: {\n      201: z.object({ id: z.number(), name: z.string() }),\n    },\n  },\n  handler: (event) =>\n    event.respond(201, {\n      id: 1,\n      name: event.validated.body.name,\n    }),\n})\n```\n\nOn the Vue page:\n\n```vue\n\u003Cscript setup lang=\"ts\">\nconst form = useEndpointForm('/api/users', {\n  method: 'post',\n  body: { name: '' },\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cform v-bind=\"form.attrs\" @submit=\"form.enhance\">\n    \u003Clabel>\n      Name\n      \u003Cinput v-bind=\"form.fields.name\" />\n    \u003C/label>\n\n    \u003Cp v-for=\"issue in form.issues.name\" :key=\"issue.message\">\n      {{ issue.message }}\n    \u003C/p>\n\n    \u003Cbutton>Create\u003C/button>\n  \u003C/form>\n\u003C/template>\n```\n\n`useEndpointForm` requires a compatible form contract. It checks body values and field names against the input schema, so a field such as `form.fields.email` would be a type error here. Build-time checks reject requirements that a native form cannot satisfy, such as a mandatory custom request header.\n\nNE generates the form attributes and connects server validation issues to `form.issues`. The [progressive-enhancement documentation](https://github.com/nuxt-endpoints/nuxt-endpoints/blob/nuxt5/docs/progressive-enhancement.md) covers the generated submission route and the browser tests with and without JavaScript.\n\n## What is available today?\n\nThe published Nuxt 4 version includes pagination, status-aware requests, Pinia Colada adapters, OpenAPI generation, and idempotency. These are ported into the module using its own implementation.\n\nThe Nuxt 5 branch is where I'm experimenting with upstream integration and progressive enhancement. It requires prototype forks. The extensions in those forks have not been accepted upstream.\n\nI'm following Nuxt's merged [fetchdts route-typing PR](https://github.com/nuxt/nuxt/pull/36238), which generates `$fetch` and `useFetch` types from reported server routes, Nitro's [removal of its previous typed-fetch implementation](https://github.com/nitrojs/nitro/pull/4572), and the [September Nitro 3 beta](https://github.com/nitrojs/nitro/releases/tag/v3.0.260903-beta). I'd like to contribute the contract introspection and type-inference pieces that downstream tools need, and reuse them in NE. The aim is less duplicated code while keeping NE's application APIs as stable as practical.\n\nYou can see the current split and setup instructions on the [Nuxt 5 progress page](https://nuxt-endpoints.github.io/nuxt-endpoints/docs/nuxt5-progress).\n\n## Why not make every remote operation a function?\n\nServer functions can expose HTTP controls, and these frameworks also support explicit API routes. I prefer starting with the route itself.\n\nFor a simple GET, I want to write a simple GET. When I need pagination or form integration, I want to opt into that abstraction for that purpose. I can see the extra requirements in the declaration and inspect the resulting HTTP interface in OpenAPI.\n\nNE still introduces conventions: `pagination` and `form` are examples. That's fine with me as long as I can see what they add and call the endpoint without the generated client.\n\nThese examples don't cover everything server functions offer. NE doesn't yet provide automatic cache revalidation after a mutation, for example. What I've been able to build so far makes me want to keep exploring this approach.\n\nWhat part of server-function DX would be hardest—or most valuable—to reproduce this way?\n\n## Links\n\n- [Nuxt Endpoints documentation](https://nuxt-endpoints.github.io/nuxt-endpoints/)\n- [Nuxt Endpoints on GitHub](https://github.com/nuxt-endpoints/nuxt-endpoints)\n- [Cursor pagination walkthrough](https://github.com/nuxt-endpoints/nuxt-endpoints/blob/main/docs/pagination-demo.md)\n- [Try the Nuxt 5 integration branch](https://github.com/nuxt-endpoints/nuxt-endpoints/tree/nuxt5#try-the-nuxt-5-integration-branch)",{"name":32,"username":33,"twitter_username":13,"github_username":34,"user_id":35,"website_url":13,"profile_image":36,"profile_image_90":37},"Yoshinori Ishii","yoshinoriishii","ishiijp",1309505,"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1309505%2Fe145ab6f-d575-4623-947b-0710090f55f1.jpg","https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1309505%2Fe145ab6f-d575-4623-947b-0710090f55f1.jpg",1788921586588]