Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypescriptCodegen.pdf

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Ovidiu Latcu Ovidiu Latcu
November 17, 2023
36

 TypescriptCodegen.pdf

Avatar for Ovidiu Latcu

Ovidiu Latcu

November 17, 2023
Tweet

Transcript

  1. Agenda • Why Typescript ? • Fetching external (remote) data

    • The problem • Open-API • GraphQL • Other alternatives
  2. Why Typescript The problem with Javascript function getUser() { return

    { id: 1, first: "John", last: "Doe", email: "[email protected]", }; } function greetUser() { const user = getUser(); console.log(`Hello ${user.first} ${user.last}`); }
  3. Why Typescript The problem with Javascript function getUser() { return

    { id: 1, firstName: "John", lastName: "Doe", email: "[email protected]", }; } function greetUser() { const user = getUser(); console.log(`Hello ${user.firstName} ${user.lastName}`); }
  4. Why Typescript The problem with Javascript function emailOrderToUser(orderId) { const

    user = getUser(); sendEmail( `Thank you ${user.first} ${user.last} for placing your order with us!` ); }
  5. Why Typescript The problem with Javascript function emailOrderToUser(orderId) { const

    user = getUser(); sendEmail( `Thank you ${user.first} ${user.last} for placing your order with us!` ); } Our refactor broke this! And there is now compiler in JS to warn us.
  6. Why Typescript The problem with Javascript function getUser(){ return {

    id: 1, firstName: "John", lastName: "Doe", email: "[email protected]", }; }
  7. Why Typescript The problem with Javascript type User = {

    id: number; firstName: string; lastName: string; email: string; }; function getUser(): User { return { id: 1, firstName: "John", lastName: "Doe", email: "[email protected]", }; }
  8. Why Typescript The problem with Javascript type User = {

    id: number; firstName: string; lastName: string; email: string; }; function getUser(): User { return { id: 1, firstName: "John", lastName: "Doe", email: "[email protected]", }; } function emailOrderToUser(orderId: number) { const user = getUser(); sendEmail( `Thank you ${user.first} ${user.last} for placing your order with us!` ); }
  9. Why Typescript The problem with Javascript type User = {

    id: number; firstName: string; lastName: string; email: string; }; function getUser(): User { return { id: 1, firstName: "John", lastName: "Doe", email: "[email protected]", }; } function emailOrderToUser(orderId: number) { const user = getUser(); sendEmail( `Thank you ${user.firstName} ${user.lastName} for placing your order with us!` ); }
  10. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo = await response.json(); }
  11. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo = await response.json(); // ^? const userInfo: any } 🫠
  12. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; };
  13. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; }; ❗❗We are casting ‘any’ to ‘User’ This has no type safety guarantee
  14. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; }; ❗❗We are casting ‘any’ to ‘User’ This has no type safety guarantee If the API changes this will not fail or warn us in any way. ⚠
  15. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; }; ❗❗We are casting ‘any’ to ‘User’ This has no type safety guarantee If the API changes this will not fail or warn us in any way. ⚠ We must update/maintain our types every time API changes ⚠
  16. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } const UserSchema = z.object({ id: z.number(), email: z.string(), }); type User = z.infer<typeof UserSchema>;
  17. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = UserSchema.parse(await response.json()); } const UserSchema = z.object({ id: z.number(), email: z.string(), }); type User = z.infer<typeof UserSchema>;
  18. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = UserSchema.parse(await response.json()); } const UserSchema = z.object({ id: z.number(), email: z.string(), }); type User = z.infer<typeof UserSchema>; This parsing will fail if API changes. It ensure we are receiving the correct type. Better. ✅
  19. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = UserSchema.parse(await response.json()); } const UserSchema = z.object({ id: z.number(), email: z.string(), }); type User = z.infer<typeof UserSchema>; This parsing will fail if API changes. It ensure we are receiving the correct type. Better. ✅ We still need to manually update/maintain our schema ⚠
  20. Issues with this approach • When API changes, somebody has

    to inform us to update our types • We need to manually update our types each time API changes • Manual work is involved, leaving room for errors
  21. Ideal setup • We automatically detect when API has changed

    • We automatically generate client types based on API schema • Everything is done automatically, no manual work involved
  22. OpenAPI Specification • Formerly known as the Swagger Speci fi

    cation • Speci fi cation de fi ning the API request/response formats • JSON or YAML • Serves as an API Documentation • Tools and code generators for client SDKs
  23. OpenAPI Specification Example components: schemas: User: type: object required: -

    id - email properties: id: type: number example: 1 firstName: type: string example: John lastName: type: string example: Doe email: type: string format: email example: [email protected]
  24. OpenAPI Specification Example paths: /userinfo: get: summary: Get user information

    description: Retrieves details about a user. responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/User" components: schemas: User: type: object required: - id - email properties: id: type: number example: 1 firstName: type: string example: John lastName: type: string example: Doe email: type: string format: email example: [email protected]
  25. OpenAPI specifications Creating an OpenAPI YAML or JSON • Manual

    adding and editing the spec fi le • Online API design tools (StopLight, Insomnia, PostMan) • Generate from code annotations
  26. Code annotations Javascript/Typescript /** * @openapi * /api/userinfo: * get:

    * summary: Get user information * description: Retrieves details about a user. * responses: * '200': * description: Successful response * content: * application/json: * schema: * $ref: '#/components/schemas/User' */ export async function GET(request: Request) { return Response.json({ id: 1, firstName: "John", lastName: "Doe", email: "[email protected]", }); }
  27. Code annotations Java import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import

    io.swagger.annotations.ApiResponses; @Api(tags = "User Operations") @RestController @RequestMapping("/api") public class UserController { @ApiOperation(value = "Get user information", notes = "Retrieves details about a user.") @ApiResponses({ @ApiResponse(code = 200, message = "Successful response", response = User.class), @ApiResponse(code = 404, message = "User not found", response = ErrorResponse.class) }) @GetMapping("/userinfo") public ResponseEntity<User> getUserInfo() { User user = new User(); user.setId(1L); user.setFirstName("John"); user.setLastName("Doe"); user.setEmail("[email protected]"); return ResponseEntity.ok(user); } }
  28. OpenAPI Generating client SDK / Types • We have generated

    our Open API speci fi cation • We can also generate client SDK or types • Language agnostic ( YAML -> Java, Typescript, Kotlin, Swift, GO, etc ) • Multiple open source tools to facilitate this • We get typed models and endpoints • Automatic code generation • Saves a lot of development time
  29. OpenAPI client SDK generation Sd Open API Spec JSON /

    YAML Sd Client SDK / Types Typescript Kotlin Swift
  30. OpenAPI client SDK generation components: schemas: User: type: object required:

    - id - email properties: id: type: number example: 1 firstName: type: string example: John lastName: type: string example: Doe email: type: string format: email example: [email protected] export interface components { schemas: { User: { /** @example 1 */ id: number; /** @example John */ firstName?: string; /** @example Doe */ lastName?: string; /** * Format: email * @example [email protected] */ email: string; }; //… }; export type User = components['schemas']['User']
  31. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; firstName: string; lastName: string; };
  32. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; firstName: string; lastName: string; }; //generated schema.d.ts from OpenAPI specs export interface components { schemas: { User: { id: number; firstName?: string; lastName?: string; /** * Format: email * @example [email protected] */ email: string; }; }; } export interface paths { "/api/userinfo": { /** * Get user information * @description Retrieves details about a user. */ get: { responses: { /** @description Successful response */ 200: { content: { "application/json": components["schemas"] ["User"]; }; }; }; }; }; }
  33. Fetching external data async function fetchUserData() { const response =

    await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = { id: number; email: string; firstName: string; lastName: string; }; //generated schema.d.ts from OpenAPI specs export interface components { schemas: { User: { id: number; firstName?: string; lastName?: string; /** * Format: email * @example [email protected] */ email: string; }; }; } export interface paths { "/api/userinfo": { /** * Get user information * @description Retrieves details about a user. */ get: { responses: { /** @description Successful response */ 200: { content: { "application/json": components["schemas"] ["User"]; }; }; }; }; }; } We no longer need to maintain our own type ✅
  34. Fetching external data import { components } from "../dist/schema"; async

    function fetchUserData() { const response = await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = components["schemas"]["User"]; //generated schema.d.ts from OpenAPI specs export interface components { schemas: { User: { id: number; firstName?: string; lastName?: string; /** * Format: email * @example [email protected] */ email: string; }; }; } export interface paths { "/api/userinfo": { /** * Get user information * @description Retrieves details about a user. */ get: { responses: { /** @description Successful response */ 200: { content: { "application/json": components["schemas"] ["User"]; }; }; }; }; }; } We no longer need to maintain our own type ✅
  35. Fetching external data import { components } from "../dist/schema"; async

    function fetchUserData() { const response = await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = components["schemas"]["User"]; //generated schema.d.ts from OpenAPI specs export interface components { schemas: { User: { id: number; firstName?: string; lastName?: string; /** * Format: email * @example [email protected] */ email: string; }; }; } export interface paths { "/api/userinfo": { /** * Get user information * @description Retrieves details about a user. */ get: { responses: { /** @description Successful response */ 200: { content: { "application/json": components["schemas"] ["User"]; }; }; }; }; }; } We no longer need to maintain our own type ✅ We import types from our generated types ✅
  36. Fetching external data import { components } from "../dist/schema"; async

    function fetchUserData() { const response = await fetch(API_URL + "/userinfo"); const userInfo: User = await response.json(); } type User = components["schemas"]["User"]; //generated schema.d.ts from OpenAPI specs export interface components { schemas: { User: { id: number; firstName?: string; lastName?: string; /** * Format: email * @example [email protected] */ email: string; }; }; } export interface paths { "/api/userinfo": { /** * Get user information * @description Retrieves details about a user. */ get: { responses: { /** @description Successful response */ 200: { content: { "application/json": components["schemas"] ["User"]; }; }; }; }; }; } We no longer need to maintain our own type ✅ We import types from our generated types ✅ We easily keep types in SYNC ✅
  37. Open API Specification Advantages • Easy way of documenting your

    BE APIs • Generated Swagger API documentation • Generated client SDK / types • Client and BE use the same types / de fi nitions • Many things can be automated and generated • Systems using up to date types, making everything more robust and safe
  38. GraphQL • Query Language for APIs • Declarative data fetching

    • Allows clients to request speci fi c data • Helps avoid fetching un-necessary data • Helps avoid multiple fetches
  39. GraphQL Schema • Schema Driven development • GraphQL APIs driven

    by a strongly typed schema • Schema de fi nes data types • Schema lists available operations / queries • Serves as a contract between client and server • Promotes easier collaboration and keeping data types in sync
  40. GraphQL Schema de fi nition language type User { id:

    Float! firstName: String lastName: String email: String! } type Query { getUserInfo: User }
  41. GraphQL Schema de fi nition language type User { id:

    Float! firstName: String lastName: String email: String! friends: [User] } type Query { getUserInfo: User getFriendsSuggestions: [User] }
  42. GraphQL Schema de fi nition language type User { id:

    Float! firstName: String lastName: String email: String! friends: [User] } type Query { getUserInfo: User getFriendsSuggestions: [User] } type Mutation { addFriend(userId: Float!): User }
  43. GraphQL Writing a query type User { id: Float! firstName:

    String lastName: String email: String! friends: [User] } type Query { getUserInfo: User getFriendsSuggestions: [User] } type Mutation { addFriend(userId: Float!): User } query GetUserInfo { } Name the query
  44. GraphQL Writing a query type User { id: Float! firstName:

    String lastName: String email: String! friends: [User] } type Query { getUserInfo: User getFriendsSuggestions: [User] } type Mutation { addFriend(userId: Float!): User } query GetUserInfo { getUserInfo } Name the query Choose the operation
  45. GraphQL Writing a query type User { id: Float! firstName:

    String lastName: String email: String! friends: [User] } type Query { getUserInfo: User getFriendsSuggestions: [User] } type Mutation { addFriend(userId: Float!): User } query GetUserInfo { getUserInfo { id email firstName lastName } } Select the desired fi elds { }
  46. GraphQL Writing a query type User { id: Float! firstName:

    String lastName: String email: String! friends: [User] } type Query { getUserInfo: User getFriendsSuggestions: [User] } type Mutation { addFriend(userId: Float!): User } query GetUserInfo { getUserInfo { id email firstName lastName } } Select the desired fi elds { } Avoiding over-fetching
  47. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `;
  48. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `; const fetchUserInfo = async () => { const response = await client.executeQuery( createRequest(UserInfoQuery, {}) ); const body = response.data; };
  49. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `; const fetchUserInfo = async () => { const response = await client.executeQuery( createRequest(UserInfoQuery, {}) ); const body = response.data; }; any 🫠
  50. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `; const fetchUserInfo = async () => { const response = await client.executeQuery<UserInfoResponse>( createRequest(UserInfoQuery, {}) ); const body = response.data; };
  51. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    … `; const fetchUserInfo = async () => { const response = await client.executeQuery<UserInfoResponse>( createRequest(UserInfoQuery, {}) ); const body = response.data; };
  52. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    … `; type UserInfoResponse = { id: string; email: string; firstName: string; lastName: string; }; const fetchUserInfo = async () => { const response = await client.executeQuery<UserInfoResponse>( createRequest(UserInfoQuery, {}) ); const body = response.data; };
  53. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `; type UserInfoResponse = { id: string; email: string; firstName: string; lastName: string; };
  54. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `; type UserInfoResponse = { id: string; email: string; firstName: string; lastName: string; }; We are again manually de fi ning our types 🫠
  55. GraphQL Writing a query in Typescript const UserInfoQuery = gql`

    query GetUserInfo { getUserInfo { id email firstName lastName } } `; type UserInfoResponse = { id: string; email: string; firstName: string; lastName: string; }; We are again manually de fi ning our types 🫠 We must keep properties names and types in sync with GQL schema
  56. GraphQL Code generator • A Tool that generates code out

    of your GraphQL schema • Generates types • Generates operations • Plugins : React, Vue, Typescript, C#
  57. GraphQL code generator Advantages • Eliminates a lot of mistakes

    • Types kept in sync with schema • Type safe access to response objects • Speeds up development
  58. GraphQL Codegenerator //codegen.ts const config: CodegenConfig = { overwrite: true,

    schema: "examples/schema.graphql", documents: "**/*.tsx", generates: { "lib/apis/": { preset: "client", plugins: [], }, "./graphql.schema.json": { plugins: ["introspection"], }, }, }; $ yarn graphql-codegen —con fi g codegen.ts
  59. GraphQL Codegenerator //Generated types file //… export type GetUserInfoQuery =

    { __typename?: "Query"; getUserInfo: { __typename?: "User"; id: number; email: string; firstName?: string | null; lastName?: string | null; }; }; //…
  60. GraphQL Codegenerator const UserInfoQuery = graphql(` query GetUserInfo { getUserInfo

    { id email firstName lastName } } `); const fetchUserInfo = async () => { const response = await client.executeQuery<GetUserInfoQuery>( createRequest(UserInfoQuery, {}) ); const body = response.data; }; //Generated types file //… export type GetUserInfoQuery = { __typename?: "Query"; getUserInfo: { __typename?: "User"; id: number; email: string; firstName?: string | null; lastName?: string | null; }; }; //…
  61. GraphQL Codegenerator const UserInfoQuery = graphql(` query GetUserInfo { getUserInfo

    { id email firstName lastName } } `); const fetchUserInfo = async () => { const response = await client.executeQuery<GetUserInfoQuery>( createRequest(UserInfoQuery, {}) ); const body = response.data; }; //Generated types file //… export type GetUserInfoQuery = { __typename?: "Query"; getUserInfo: { __typename?: "User"; id: number; email: string; firstName?: string | null; lastName?: string | null; }; }; //…
  62. GraphQL Codegenerator const UserInfoQuery = graphql(` query GetUserInfo { getUserInfo

    { id email firstName lastName } } `); const fetchUserInfo = async () => { const response = await client.executeQuery<GetUserInfoQuery>( createRequest(UserInfoQuery, {}) ); const body = response.data; }; //Generated types file //… export type GetUserInfoQuery = { __typename?: "Query"; getUserInfo: { __typename?: "User"; id: number; email: string; firstName?: string | null; lastName?: string | null; }; }; //… } {
  63. Code generation • Are you doing a lot of manual

    work ? • Is external APIs schema hard to determine ? • Is it a problem to maintain types ? • Is it hard to identify changes in external types ? • Maybe this can be automated via code generation
  64. Headless CMS • Popular Headless CMS • Types and models

    are de fi ned in a GUI • All come with code generation solutions • Allows us to bring those types to client code
  65. Databases and ORMs • Many available solutions • Generate Types

    from DBSchema • Generate type safe client SDKs • Typesafety in mind Drizzle ORM