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

TSConf 2019: Don't Break the Contract: Keep Consistency with Full Stack Type Safety

TSConf 2019: Don't Break the Contract: Keep Consistency with Full Stack Type Safety

We’ve seen the Web evolve from blue hyperlink text to complex web applications. In modern web application development, not only JavaScript is allowing our teams to build richer user experiences in the front-end, but Node.js has also emerged as the go-to back-end technology of choice.

While this stack is flexible and fun to program in, building and maintaining internal APIs can be an unpleasant experience. Have you ever had a front-end bug because there was an unexpected change in the back-end? In this talk, we’ll explore how TypeScript can help our development teams to reduce these bugs by extending the type system from the back-end to the front-end.

We'll discover how types bring benefits to an application in terms of maintainability, robustness, and clean code that can be easily refactored in the future.

053151a066bcaecbc1480644d7cbd1e1?s=128

Fernanda Andrade

October 11, 2019
Tweet

Transcript

  1. Don’t Break the Contract: Full Stack Type Safety with TypeScript

    @feru @flandrade
  2. Consumer Provider Contract

  3. Contract

  4. { name: "Tess", price: 11 }

  5. { name: "Tess", prices: [11, 9] }

  6. { name: "Tess", prices: [11, 9] }

  7. Contract Modify data structures

  8. How do we solve it?

  9. Integration tests

  10. Provider

  11. Provider Consumer

  12. Request Response Provider Consumer

  13. Integration tests are expensive slow reliable

  14. Contract tests

  15. Request Response Provider

  16. Request Response Provider Contract

  17. Request Response Consumer Contract

  18. Contract tests are less expensive less slow less reliable

  19. Types with Project References

  20. client tsconfig.json server tsconfig.json

  21. client tsconfig.json server tsconfig.json shared-types tsconfig.json (Contract)

  22. client tsconfig.json server tsconfig.json shared-types tsconfig.json

  23. client tsconfig.json server tsconfig.json shared-types tsconfig.json tsconfig.json (Solution)

  24. { "references": [ { "path": "client" }, { "path": "server"

    }, { "path": "shared-types" } ], "files": [] } tsconfig.json (Solution)
  25. { "extends": "../tsconfig.base.json", "compilerOptions": { ... } }, "references": [

    { "path": "../shared-types" } ] } tsconfig.json (client)
  26. None
  27. None
  28. Types are the single source of truth

  29. declare namespace MyApi { type Status = "available" | "unavailable";

    interface User { age: number; id: number; name: string; status: Status; } interface GetUsers { (): Promise<User[]>; } ... index.d.ts (shared-types)
  30. const getUsersReq = (req, res) => { res.json(getUsersFn()); }; const

    getUsersFn: GetUsers = function() { const users: User[] = [{ age: 15, id: 10, name: "Tess", status: "available" }]; return Promise.resolve(users); }; users.ts (server)
  31. declare namespace MyApi { type Status = "available" | "unavailable";

    interface User { age: number; id: number; name: string; status: Status; } interface GetUsers { (): Promise<User[]>; } ... index.d.ts (shared-types)
  32. const getUsersReq = (req, res) => { res.json(getUsersFn()); }; const

    getUsersFn: GetUsers = function() { const users: User[] = [{ age: 15, id: 10, name: "Tess", status: "available" }]; return Promise.resolve(users); }; users.ts (server)
  33. declare namespace MyApi { type Status = "available" | "unavailable";

    interface User { age: number; id: number; name: string; status: Status; } interface GetUsers { (): Promise<User[]>; } ... index.d.ts (shared-types)
  34. const getUsersReq = (req, res) => { res.json(getUsersFn()); }; const

    getUsersFn: GetUsers = function() { const users: User[] = [{ age: 15, id: 10, name: "Tess", status: "available" }]; return Promise.resolve(users); }; users.ts (server)
  35. index.d.ts (shared-types) declare namespace MyApi { type Status = "available"

    | "unavailable"; interface User { age: number; id: number; nickname: string; status: Status; } ...
  36. None
  37. None
  38. None
  39. None
  40. ✓ Contracts change with types ✓ Build fails if server

    and client don’t comply
  41. Logical separation between components

  42. client tsconfig.json server tsconfig.json shared-types tsconfig.json

  43. ✓ Separation of concerns ✓ Loosely-coupled packages ✓ No cross-referenced

    copied code
  44. No confusion about package version

  45. Write Publish Package A With dependencies Update Write Package B

  46. Write Publish Package A With project references Update Write Package

    B
  47. ✓ No need to publish a package ✓ No extra

    tools to handle versions
  48. Take advantage of your types

  49. None
  50. Types are the single source of truth

  51. Thank you! @feru @flandrade