DTOs are Essential in the Frontend | TypeScript
Summary
TLDRThis video script discusses the importance of Data Transfer Objects (DTOs) in both backend and frontend development. It explains how DTOs transform raw data into a usable format, emphasizing their role in API design and data handling. The script also addresses common mistakes in state management within components and advocates for the use of DTOs to maintain code readability and maintainability. It provides practical examples of creating DTOs, including handling nullable values and using discriminated unions, to demonstrate the benefits of DTOs in streamlining data processing and reducing bugs.
Takeaways
- 🔄 Data Transfer Objects (DTOs) are essential for both backend and frontend development, transforming raw or sensitive data into a structure that can be safely consumed.
- 🔑 DTOs are crucial in frontend development for transforming raw API data into a usable format, reducing the need to derive state within components.
- 🚫 Avoid deriving state within components as it leads to the creation of numerous functions, making the codebase harder to maintain and understand.
- 🗃️ Use a data access directory to abstract API interaction, holding all logic for queries, mutations, and policies.
- ✅ Always validate external data at runtime, even if it comes from a reliable source, to facilitate easier debugging and logging.
- 🔄 Normalize data structures to handle inconsistencies and avoid tedious handling of nullable fields and arrays represented as tuples.
- 🆔 Utilize nominal types to differentiate between similar underlying types (like IDs), minimizing bugs during data manipulation.
- 🎛️ Remap API data to user-centric structures in the DTO to make the frontend code more intuitive and user-friendly.
- 📊 Use discriminated unions to handle complex data scenarios, allowing conditional fields and behavior based on the data context.
- 🚀 Employ functional programming techniques like option and match from libraries like Effect to streamline nullable and conditional data handling.
- 🛠️ Ensure all components consuming API data use the DTO structure, making the data consistent across the application.
- 🗃️ Use the same DTO structure for both incoming data and cache manipulation to maintain consistency and reduce errors.
Q & A
What is the primary purpose of Data Transfer Objects (DTOs) in back-end development?
-DTOs serve as a means to transform potentially raw or sensitive data into a structured format that can be safely consumed, acting as a cornerstone of a well-designed API.
How are DTOs utilized in front-end development, and why are they important?
-In front-end development, DTOs are essential for transforming the raw data received from APIs into a sensible and usable format, enhancing the readability and maintainability of the project.
What is the common mistake made by many projects in terms of state management within components?
-A common mistake is overusing state within components, which can lead to hundreds of functions and custom hooks being created, spreading all over the codebase and reducing maintainability.
Why is it necessary to validate data from external sources at runtime, even if the API is autogenerated or introspected?
-Validating data at runtime is crucial for quick debugging and ensuring that the data conforms to expected schemas, which helps in catching bugs regardless of the source of the data.
What is the significance of using branded types or nominal types in DTOs?
-Branded types or nominal types extend base types with additional information, allowing for type safety and preventing the comparison of different types that share the same underlying type.
How does the script suggest organizing data access logic in a front-end application?
-The script suggests creating a data access directory to hold all the logic for the API, including query policies and other business logic, abstracting this layer for better organization.
What is the role of discriminated unions in DTOs as discussed in the script?
-Discriminated unions are used to normalize and remap data types based on certain conditions, making it easier to handle complex data structures and ensuring type safety.
Why should DTOs be used for both incoming and outgoing data manipulation in an application?
-Using DTOs for both incoming and outgoing data ensures consistency and type safety throughout the application, making it easier to communicate and manipulate data across different components.
How does the script handle nullable values in DTOs to improve code readability?
-The script uses the Option type from functional programming, which represents the presence or absence of a value, to handle nullable values elegantly and improve code readability.
What is the benefit of using the 'match' pattern with discriminated unions in DTOs?
-The 'match' pattern with discriminated unions allows for a switch-like statement that is more powerful and type-safe, ensuring all possible values are handled and reducing the chance of runtime errors.
Outlines
📚 Data Transformation with DTOs in API and Frontend Development
This paragraph discusses the importance of Data Transfer Objects (DTOs) in both backend and frontend development. DTOs are instrumental in transforming raw or sensitive data into a structured format that can be safely consumed. The speaker argues that DTOs are a cornerstone of well-designed APIs and are equally vital for frontend development, particularly for transforming API data into a usable format for components. The paragraph also touches on common mistakes made with state management in components and the benefits of using DTOs to avoid redundancy and improve code readability and maintainability.
🔧 Utilizing DTOs for Data Normalization and Type Safety
The speaker elaborates on the use of DTOs for data normalization and ensuring type safety in applications. They describe how DTOs can help in transforming data from external sources, such as APIs, into a format that is more suitable for frontend consumption. The paragraph emphasizes the importance of data validation at runtime to quickly catch bugs and the use of nominal types or branded types to prevent errors related to data type mismatches. It also explains how DTOs can simplify the process of handling complex data structures and conditional logic in client-side applications.
🛠 Implementing Discriminated Unions and Type Guards with DTOs
This section delves into the technical implementation of DTOs, particularly the use of discriminated unions for handling complex data structures. The speaker introduces the concept of branded types for distinguishing between different types of IDs and the use of type guards to ensure that the correct data types are being manipulated. They provide an example of transforming raw post data into a DTO format, including handling optional fields like edit history and author information, and demonstrate how to use pattern matching to simplify conditional logic based on data types.
📝 Advanced Data Transformation Techniques with Options and Arrays
The paragraph explores advanced data transformation techniques using Options and Arrays from the Effect system, which are used to handle nullable values elegantly. The speaker demonstrates how to streamline data transformation by using functions like 'fromNullable', 'getOrElse', and 'option.map' to create clean and readable code. They show practical examples of transforming data structures, such as comments and edit histories, into a normalized format that is easy to consume in the application's components.
🔄 Applying DTOs for Caching and Mutations in Frontend Applications
The final paragraph wraps up the discussion by emphasizing the importance of using DTOs not only for incoming data transformation but also for manipulating the cache in frontend applications. The speaker explains how to ensure that the server sends data in a format that can be used to update the cache accurately after mutations, such as adding a post. They conclude by highlighting the need for consistency in using DTOs throughout the application for both data input and cache manipulation to maintain data integrity and simplify development.
📢 Conclusion and Call to Action
In conclusion, the speaker summarizes the video's content and encourages viewers to subscribe and like the video for more similar content. They look forward to connecting with the audience in the next video, indicating the end of the current discussion on DTOs in API and frontend development.
Mindmap
Keywords
💡Data Transfer Objects (DTOs)
💡API
💡Frontend Development
💡State Derivation
💡Data Validation
💡TypeScript
💡GraphQL
💡Normalization
💡Discriminated Unions
💡Options
Highlights
Data Transfer Objects (DTOs) are essential in both back-end and front-end development for transforming data into a safe and usable format.
DTOs are a cornerstone of well-designed APIs, ensuring data is structured for safe consumption.
In front-end development, DTOs transform raw data from APIs into a sensible format, despite potential redundancy with back-end DTOs.
Minimizing state derivation within components is crucial for maintainability and readability.
Creating reusable functions or custom hooks for state derivation can lead to hundreds of functions scattered throughout a project.
A data access directory is recommended for organizing API logic and query policies.
Validating data from external sources at runtime is mandatory for quick debugging and ensuring data integrity.
Branded types or nominal types in DTOs help prevent bugs by distinguishing between similar underlying types.
DTOs should remap API data to better suit the client's perspective and user interaction.
Discriminated unions in DTOs help manage complex data structures and conditional logic.
Normalization of data, such as converting 'nullish' values to 'null', simplifies data handling in DTOs.
Using options in TypeScript can make code more elegant and readable, especially for nullable values.
The power of options in TypeScript is demonstrated through complex examples, showing its superiority over traditional ternary operations.
DTOs should be used consistently throughout an application for both incoming data transformation and cache manipulation.
The importance of using DTOs for mutations and cache updates is emphasized for data consistency.
A comprehensive example demonstrates the transformation of raw posts into a structured DTO format for easy consumption.
The video concludes with the recommendation to subscribe for more content on DTOs and front-end development.
Transcripts
dto or data transfer objects they are
essential in buck and development they
serve as a means to transform
potentially raw or sensitive data into a
structure that can be safely consumed I
would say it's a Cornerstone of a
well-designed API but I would even go as
far as to argue that they are equally
essential to frontend development of
course not in terms of security or
anything like that as everything in the
front end ultimately is accessible to
anyone rather in transforming the raw
data that you receive from these API
into a sensible and usable format now
this is a little bit ironic since the
backend might already employ a dto so
are we slapping a DPO on top of another
dto no that's not how it works I've seen
many but many people many projects make
the mistake of theing all possible state
within their components now is deriving
state within a component wrong I'm not
saying that I'm saying that you can
minimize that if you start using dto so
take this for example we have this query
right here we use this in a component we
need to derive this state in the Heather
so we passing the query data over to the
Heather and the Heather transforms the
data and deres a state from it but now
you realized that you need that same
data within another deeply nested
component what do you do in that case
well simple you create either a reusable
function or you create a custom hook but
you start doing that for every single
piece that you need to derive so now
you're creating hundreds of functions
and you're spreading them all over the
place and you need to be constantly
recalling which function does what and
which function you need for that and
that will just trust for the legibility
and maintainability of your project so
please don't do that of course there are
some cases where it's unavoidable but
generally speaking you should always be
using dto so here I have a simple
example I have a wrapper for our use
Query hook which you should always be
doing if you're using something like s
query what I like to do is create a data
access directory and here I hold
everything that is the layer for the API
so here I can have all of the re right
policies the logic for the queries
enabled whatever you should always be
abstracting this anyway here I can pass
in some options so the query variables
then I pass them over to the query key
if you've used s query well you would
know why this is very important and then
here in the query function I retrieve
the query variables and pass them over
to the fetcher then all we do is try to
parse the body expecting Json and then
we decode the data now this step is
mandatory all data from any type of
external Source should always be
validated at the run time and there are
no excuses for this even if this is an
autogenerated open API spec or
introspected with a graphql Cogen you
should always validate the data and why
because it lets you debug really fast if
you have logging in place with your run
and validators it is very easy to catch
any kind of bugs is your schema wrong
your types or is the server hailing at
certain scenarios there's a plethora of
different cases where if you validate
from the very beginning and that logging
on top of that you will make your life
way easier so please at all times
validate any external data whether
that's from local storage session
storage your API a third party library
in short anything that is external
anyway here we return the decoded posts
and call it a day now we can start
consuming this query we can make all
type of derivations within the
components and if we want to manipulate
the Kush well we're going to be mutating
this structure now let's come here over
to the schema so here in the post
response schema we get back an object
that contains an ID the content then the
author then the author is an object with
ID and username but notice how the
username is Nish meaning that it can be
undefined and null which makes it
incredibly tedious to work with so this
is something you should normalize to
just null then the top comment this is
an object contains an ID the content the
author type and this is an enum use
moderator or admin then we have the
author ID which again is Nish and this
in itself is Nish so we need to
normalize these two and then we have the
latest comment and this is an array of
objects and each object again has the
same structure however notice how we're
restraining this to just be one if it is
present because this can be knowledge
again now the reason why I used one as
an example is because if you have used
graphql with something autogenerated say
HRA or something of the like or an API
that is badly designed which is very
very common a single element is
represented as a topple so again this is
something that we need to normalize we
need to extract the object if it is
present it is again very tedious to work
with an array that is in fact a topple
and a topple of one element which makes
no sense whatsoever and finally we have
these two which are very interesting and
why because the edit history should only
be present if this post was created by
the current session so if the currently
logged in user didn't create this post
then we shouldn't be able to access the
edit history this is a field that should
be completely absent in other words it
should be represented by never in
typescript and same for each following
author if you create the post it doesn't
make sense that you can follow yourself
so again this should be hidden so for
that we can have a discriminator so
notice how this can become more and more
complex and that's not something you can
easily represent in an API this is
something that ultimately the client
needs to do so for this again we need to
have a discriminator and these two will
exist Bas on that discriminator and
that's it now obviously this is a very
contrived example an actual API would be
way way bigger and would have way more
business logic but with this we can see
how messy it can become if you want to
derive all of these nuances in the
components it is going to be extremely
tedious so now let's take a look at the
solution the dto so here I it starts off
with branded types or in other words
nominal types meaning types that extend
from a base type so in this case from
string string but we add something to
these types to be able to set them apart
so what I mean by this if you have two
strings and you compare to Strings that
is completely valid but if we have a
nominal type such as these ones if we
were to compare so let me show you if we
were to compare a comment ID with a post
ID even though they both share the same
underlying type they are considered to
be different and as we can see we get
type error now you might be wondering
why are we doing this this seems
overkill for the front end well in the
front end you need to do a lot of
mutations you need to pass data a lot
you need to manipulate the cash a lot
and it is very bad very easy to make
this mistake and if you want to update a
record in the cash or an optimistic
update or a settled update and you end
up passing in the wrong ID you have now
introduced a bug so if you can minimize
all type of bugs at the type level you
should do it so this is why I'm an
advocate for nominal types anyway I have
a whole video dedicated on this so make
sure to check it out now we have the
comment author type so we have a regular
user we have this stuff and we have this
system so this is essentially a
remapping of this type now you might be
wondering why are we in the dto
remapping this shouldn't it be as close
as one to one with the API and I would
say no the API is designed for data
everything in an API is centered around
the processing of data meanwhile a
client is designed to consume the data
so what the user sees is perceived
completely different from the underlying
architecture of your application so what
I always like to do is to name things as
if the user was the one interacting with
the code if you can Center it as you
being the user it makes your life easier
so this is just an example but you get
the gist of it obviously these are just
placeholders don't Focus too much on the
idea behind these names this is just so
you get my point this is a demonstration
now I told you we're going to be using
discriminated unions so I created the
base type so here nothing too fancy I
just normalized things so it is now oral
instead of or undefined oral I added in
the nominal types I Rema the author type
and that's pretty much it now as for the
discriminators I added these two types
so own post dto and other user post dto
so we have the type for own we have the
edit history and we have the other user
post dto Ty is following author Boolean
and we added an extra property like can
reports assuming this would come from
the configuration of the user so when
you query the permissions of a user
whatever this is just an example
placeholder and then the post dto is
again the union of these two intersected
with the Base Post dto now I created the
get author type so we take in an author
type which again is this Union and all
we do is use mat now if you have used
something like CS pattern this will look
very familiar and if you have no idea
what this is this is a switch statement
on steroids and this comes from effect
so effect brings much it brings array
option and a lot of other things now for
this we say we're going to match for the
other type then we restrict the type
here so we must return this and then we
have a when user remove this to regular
user moderator stuff and administrator
stuff and then we say exhaustive so that
it ensures that we're handling all
values here so we were to have a fourth
one and we do not handle that case we're
going to get a type error so again this
is great obviously this is very basic
we're just remapping them like this but
usually you would have some kind of
logic here so maybe for user there's
more than just remapping this to our
regular user and then we have the r post
posts to posts so this is the actual
function that transforms everything so
we take in a r post then we get the
current user ID so this comes from a use
session store so again nominal types
make sure we're not comparing the wrong
data types here and well we just have
the users so we retrieve that and then
we can determine if this post was
created by the current user so now we're
deriving this state here then we return
we we apply the nominal type we have the
content here this is great because you
can trim the content you can use our
rejects you can use whatever you want
here so everything is normalized via d
dto then you have the author again
nominal types and then notice this I'm
using a pipe and then I'm using option
Now options don't really make much sense
in typescript they make perfect sense in
languages like rust but if you're going
going to be using options to pass data
around in typescript would say you're
doing it wrong now if you're not
familiar with options options is simply
a way to indicate either the absence or
the presence of a value so now you might
be saying well that's null right null is
the absence of a value and if it's not
null it is present and you're completely
right so that's why I say in typescript
it doesn't make much sense because you
can narrow down the type by simply
checking against null but the reason why
I'm using option in this case is because
it can make your code very elegant
You're simply converting things to
options so in this case we can say from
Noel that way the sum so the presence of
the value is assigned whenever the
username is not null or undefined as
simple as that and then I say get or
null so get the Su value if there is one
and if the recent then simply fall back
with null now obviously this is very
basic you would say just use a ternary
for this or are you using option in my
opinion this is way more readable and
requires less mental overhead but let me
show you more complex examples so that
you can see the power of option so then
we have the top comment so again we pipe
this we save from Noel so we assign the
top comment to some if it is present and
then we can say option map so this is
great because the map allows you to
access the some value when it exists so
if I come here as we can see the comment
is now not knowledge it is present so
again map this only executes if this
results in a sum value so here we can
map this over like you would do with an
array you taking the input you output
something new derived from that value
and then here we can say ID comment ID
we pass in the nominal type the content
which Ram the content then the author
type here which actually they should use
the function that I showed you so get
author type and then I have the author
ID so again I say from Noble I assign
this to sum if it is present if it is
present I map it over I take in the sum
value so the author ID and I implicitly
convert it to this nominal type and then
I say get or not so see how clean this
is if you were to use a turnar for this
you can see right away how we have to
Nest a taries we have one for this one
which would look terrible and then you
would have another nest theary for this
so this is streamlined with option where
I Rely solely on option for nullable
values and much so this one for logical
values so this is what I would recommend
and then comment by S so this is a
property that I added so this is true if
the current user ID is equal to the
comment. author ID so as we can see
we're deriving here and now any
component can simply check this Boolean
property and say render a bdge or an
edit button whatever there's no need for
each component to derive it and then
after all of this I say C or null so if
this is null then do this if this is not
null then do this then we have the pre
give you comment so here again I pipe
this through I save from E Rubble so I
use array and this array is not the
native one this comes from effect which
works really nicely with the pipe and
with option so here I say from it Rubble
which would be array. from pretty much
the same thing and then we say array.
head and the head gets the first element
of an array or none if the array is
empty so this in itself returns an
option for us that means that all we
need to do is say option. mop and now
we're getting this if this exists as we
can see we get the comment here we do
everything again here we say from
nullable we map this over apply the
nominal type get or null this is very
very readable in my opinion and then
again comment by self so we apply the
same logic although we could extract
this into its own helper function like
we did with getor type but for it the
sake of demonstration purposes we can
leave it as is and then we have the
discriminators so if isone post then we
spread over this object where we passing
the type as own so as const we need to
narrow it down then we have the edit
history and again from no level we check
the edit history if it exists then we
can map overreach element so we say
array. map and this will implicitly take
in the value from option. map and then
we get each edit so as we can see edit
is just this object and we parse this
into a new data assuming this is an ISO
string and then the content is just
edit. content and in the case there's
nothing we default back to an empty
array so get or else and this must
satisfy own post dto and then we have
the other one other user post dto and we
do the same so is following author get
or else false so again fall bu and then
they can report which is what I was
talking about earlier well this could
depend on your business Logic the
permissions of the user whatever so this
is just a placeholder for demonstration
purposes and this is pretty much the
whole dto so now to use it all you need
to do is come here and then say return
and then you could say and this is
called raw posts this is a actually raw
post or you could say post dto you can
come up with whatever name I can't come
up with one at this instance I'm going
to keep it with a very basic name which
isn't good whatsoever and then this
would be the decoded post I import it
and then I can come here to this type
and then replace this with the post dto
and this should be post dto so now when
you cons assume this query we going to
get this data already derived now what
about mutations what if you need to add
a post and then you need to get the data
back how do you update the cache
accordingly well all you need to do is
obviously make sure that the server
sends in the same data so that you can
replace the optimistic post or at least
partial data and then you can make up
some data in the Kush and all you need
to do is call this function so post dto
and you store the cache data with this
structure so that means that all points
in your applications that need to
communicate or manipulate the cach must
use these dto so not only everything
incoming needs to be transformed using a
dto anytime you manipulate the cash you
need to also par the data and apply the
dto there anyway this wraps up the video
If you want to see more content like
this make sure to subscribe and like the
video I'll see you in the next one see
you
浏览更多相关视频
Entity Framework Setup - GRAPHQL API IN .NET w/ HOT CHOCOLATE #5.1
The Right Way To Build REST APIs
O que é uma arquitetura de uma aplicação web?
#5 Data Types in JavaScript - 1 | JavaScript Tutorial
Features of Object Oriented Programming Part 2 | C ++ Tutorial | Mr. Kishore
Variable Basics (Updated) | Godot GDScript Tutorial | Ep 1.1
5.0 / 5 (0 votes)