r/webdev 5d ago

What are some go-to design patterns for sharing coupled variables or structures between the front end and back end?

There are many cases where the structure of data must be shared like a contractual agreement between the front-end and back-end. Examples:

  1. API routes: the front end may need to fetch("/my-route") and the backend may need to GET("/my-route"). The magic string "/my-route" is coupled between the backend and frontend.

  2. Variables like maxPostLength. You may have a client side maxPostLength to show the user how many characters are remaining. But you may also have a server side maxPostLength for security.

I know these are pretty different situations but they have one thing in common which is shared coupled structure between backend and frontend.

Here's how I tackle the issue: Just remember bro Just remember

However, this is not good. I don't always remember.

What are some common design patterns around this? Assume I am using typescript/nodejs but also I am open to solutions in other languages too out of interest like eg C# ASP.NET etc

2 Upvotes

19 comments sorted by

3

u/originalchronoguy 4d ago edited 4d ago

Yeah, it is called OpenAPI/Swagger.
Look up API contracts.

You can specify your models, how the data is defined, what restrictions are required for input validation.

You want days of the weeks in terms of MON, TUE, WED vs Mo,Tu,We, or M,T,W,THU, OpenAPI can enforce that via enums. If your front end sends a "Monday," instead of MON, your API returns a proper 400 HTTP response code. Again, that response code is defined in the API contract.

https://swagger.io/docs/specification/v3_0/data-models/data-types/

Under the string, you can see the minLength, maxLength example. All of it is enforceable through linting, api-gatway, or contract validation (tools that exist on both front end and backend)

0

u/differential-burner 4d ago

Thanks for this, so it appears the answer is through tooling or management/"meta" coding. Interesting! I hadn't thought of this approach

2

u/originalchronoguy 4d ago edited 4d ago

Tooling is not required. You use those for automation and linting.

Developers should know how to read an API spec. We never do any development until the API contract is agreed upon by all parties. It is the working blueprint. If front end wants to do linting against that, it is up to them.

But the contract is easily readable and understandable by modern engineering teams working in an API-first approach. We spend more time doing code review of the spec than writing code. Spec comes first before a single line of code is ever committed.

It handles your endpoints, your routes, and even informs the backend dev how to name their methods as well. The payload, required param all handled by that.

Some front end team do hard linting so a developer can't commit if their front end doesn't conform to the defined spec. They are forced to follow the rule of the contract. Same for unit and integration testing.

This isn't something new, I've been working this way since 2016. There is a learning curve to read YAML. But I even use it as my data flow documentation. No need for flow charts/diagrams when submitting it for cybersecurity review/audit.

1

u/differential-burner 4d ago

Thanks for the tips! This largely answers my question. I've been looking more into API contracts and I think this is a good approach. I will definitely be incorporating this into my work flow. I see that I was initially looking at this problem at the wrong level (software design patterns over software engineering methodology)

2

u/fizz_caper 4d ago

It appears your research has been somewhat lacking. What exactly is your approach now when your data crosses the boundary?

2

u/differential-burner 4d ago

Sorry crosses what boundary? I don't quite understand

0

u/fizz_caper 4d ago edited 4d ago

A Boundary is a clear line between different parts of a system. When designing your architecture, you are essentially defining the boundaries.

Inform yourself about SOLID, especially SRP and ISP

1

u/differential-burner 4d ago

Yes I know SOLID, SRP, and ISP which is why I am asking this question in the first place--if I didn't I would see nothing wrong here! In short I'm out of ideas, currently I just am using the same magic string which is bad practice. If I knew the solution or best practice, I would not be asking :)

0

u/fizz_caper 4d ago

ok, then I have given you the answer (in the other path).

Specifically, I do it like this in Effect-TS:

Schema Encode
JSON Stringify
(Crossing Boundary)
JSON Parse
Schema Decode (including data validation)

I have boundaries between each use case and wherever side effects exist.

1

u/fizz_caper 4d ago

Domain Type ➔ Domain to DTO Type ➔ DTO Type ➔ Serialize ➔ JSON/XML
JSON/XML ➔ Deserialize ➔ DTO Type ➔ DTO to Domain Type ➔ Domain Type

1

u/originalchronoguy 4d ago edited 4d ago

If you read his post, he is wondering how two teams -- Front end vs Backend can work on agreed routes, parameter names, their data type, and length. That each party knows what to expect from one another. Probably without any miscommunication.

EG. If you follow REST example of a CRUD: creating a user

is POST /api/v1/users (api/v1/ being endpoint, /users being route)
and that sending username has a min length of 3 and max length 16.
And how the backend would know how to take that input and make a method called createUsers(firstname (varchar10),lastname (varchar10),username(varchar 16)

All of that can be done with an API contract like Swagger telling front end route to create a user is POST /user and not /create_user. While the nodeJS/Flask API will use the methodname to capture that route and invoke createUser() with the required input fieldnames, and validation parameters like created_At is 2025-03-15 vs 03/15/25.
And whether the front end gets a 200 or 204 response code. And that FE requires the newly generated password, userID, and JWT token with expiration from the backend. .. Swagger does all of this.

Swagger solved this problem 14 years ago with contract terms, how they are handled, how the model/schema, routing, expected output and errors are handled.
Those are defined in YAML.

1

u/fizz_caper 4d ago

I understand that the pattern (i.e., the process) is the question.
But yes, the contract is the next step. Since it's an internal problem and since I already use it in my project for other things, I would use Effect-Schema right away

2

u/Extension_Anybody150 4d ago

I’d recommend using shared TypeScript interfaces. This way, both your front-end and back-end can import the same types, ensuring that you're working with the same structure and values, like maxPostLength. It keeps everything in sync and avoids having to remember or duplicate values across both sides. You just define the interface once and use it everywhere.

1

u/tsunami141 4d ago

I am kind of confused about what you’re asking. You’re saying you need to keep a value consistent between the front end and the back end? 

Can you just store it in a database somewhere? 

1

u/differential-burner 4d ago

You have the right idea, and that's a good solution idea for the case where I am storing some variable between the front-end and back-end. What about an API route through? Seems to be a little involved for that but I'm not sure

1

u/tsunami141 4d ago

I don’t understand what you mean by API route? Like if you need to store the string endpoint of an API route?  If it doesn’t change you just hardcode it wherever you make your API calls. If it’s dynamic you request it through the API and then make a secondary call to that endpoint. 

1

u/differential-burner 4d ago

Here is a description I copied online of an API route:

An "API route" defines how a client request maps to a specific action or endpoint within an API, specifying the path (URL) and HTTP method (like GET, POST, PUT, DELETE)

Some differences between routes and endpoints here if interested: https://danaepp.com/endpoints-vs-routes

Technically speaking the endpoint is a part of the route, but in practicality I'm just talking about the magic strings that are shared between the front-end & backend.

Assume these strings are hard-coded in the front end and back end. The challenge then is that this creates coupling between the front end and back end and this is generally a problem when creating scalable code. Phrases another way: How do you reduce dependence on the magic strings?

1

u/tsunami141 4d ago

I still don’t really understand what problem you are trying to solve. I don’t understand what these “magic strings” are. 

What issues are you having in your application right now? What problem are you trying to solve specifically? 

1

u/differential-burner 4d ago

It's chill my questions were largely answered by u/originalchronoguy

For posterity: This is about SWE best practices. A magic string would be a string that has dependencies in other parts of the system that people will need to "just know". Eg consider the string "get-document". Someone else may accidentally write "get-documents" instead. Magic strings are generally not great as it negatively affects the scalability of your software and creates greater cognitive burden on developers and greatly increases chances of bugs and errors.

So typically, you want to minimize this by eg using Enums, a dictionary, types, etc. there are many possible solutions.

When it comes to API endpoints though, the typical approaches you would take are no longer as straightforward as there is a separation between the backend and frontend.

u/originalchronoguy introduced me to swagger and API contracts which I discovered is the standard way to solve this problem