RSS
 

Data Transfer Objects and NextJS Server Actions

01 Aug

Next.js server actions can be leveraged to implement the DTO (Data Transfer Object) design pattern to structure and manage data flow between the client and server.

What Are Data Transfer Objects (DTO)s?

DTOs are objects that define the structure of data being transferred between layers of an application (e.g., client and server). They are typically used to:

  1. Encapsulate Data: Define a clear contract for the shape of data being sent or received.
  2. Validate Data: Ensure that the data conforms to a specific structure.
  3. Decouple Layers: Abstract the internal structure of the server or database from the client.

DTOs with Next.js Server Actions

Server actions in Next.js provide a way to handle server-side logic directly in the application. By using DTOs as interfaces for server actions, you can:

  1. Standardize Data Contracts
    • Define clear and reusable interfaces for the data sent to and received from server actions.
    • Ensure consistency across the application.
  2. Improve Type Safety
    • Use TypeScript to enforce the structure of data at compile time.
    • Reduce runtime errors caused by unexpected data shapes.
  3. Simplify Validation
    • Validate incoming and outgoing data against the DTO structure.
    • Use libraries like zod or class-validator for runtime validation.
  4. Enhance Maintainability:
    • Centralize data definitions, making it easier to update and refactor the application.

How to Implement DTOs with Server Actions in Next.js

Step 1: Define DTO Interfaces

Create TypeScript interfaces or types to define the structure of the data being transferred.

import { z } from "zod";

// Define the DTO schema using zod for runtime validation
export const SheetUserWeightsDTO = z.object({
  userId: z.string(),
  sheetId: z.string(),
  specWeights: z.record(z.string(), z.number()), // Example: { spec1: 0.5, spec2: 0.8 }
  specsVersion: z.number(),
});

// Infer the TypeScript type from the zod schema
export type SheetUserWeightsDTOType = z.infer<typeof SheetUserWeightsDTO>;

Step 2: Use DTOs in Server Actions

Leverage the DTOs in server actions to validate and enforce the structure of incoming and outgoing data.

import { SheetUserWeightsDTO, SheetUserWeightsDTOType } from "@/lib/dto/sheetUserWeights.dto";

export async function saveSheetUserWeightsAction(data: SheetUserWeightsDTOType): Promise<void> {
  // Validate the incoming data
  const parsedData = SheetUserWeightsDTO.parse(data);

  // Perform the server-side logic (e.g., save to database)
  const { userId, sheetId, specWeights, specsVersion } = parsedData;

  // Example: Save to database
  await db.collection("sheetUserWeights").updateOne(
    { userId, sheetId },
    { $set: { specWeights, specsVersion } },
    { upsert: true }
  );
}

export async function getSheetUserWeightsAction(userId: string, sheetId: string): Promise<SheetUserWeightsDTOType | null> {
  // Fetch data from the database
  const result = await db.collection("sheetUserWeights").findOne({ userId, sheetId });

  if (!result) return null;

  // Validate the fetched data
  return SheetUserWeightsDTO.parse(result);
}

Step 3: Use DTOs in Client-Side Hooks

Use the DTOs in React Query hooks to ensure type safety and consistency.

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { SheetUserWeightsDTOType } from "@/lib/dto/sheetUserWeights.dto";
import { getSheetUserWeightsAction, saveSheetUserWeightsAction } from "@/lib/actions/sheets";

export function useQuerySheetUserWeights(userId: string, sheetId: string) {
  return useQuery<SheetUserWeightsDTOType | null, Error>({
    queryKey: ["sheetUserWeights", userId, sheetId],
    queryFn: () => getSheetUserWeightsAction(userId, sheetId),
    enabled: !!userId && !!sheetId,
    staleTime: 1000 * 60 * 5, // Cache for 5 minutes
  });
}

export function useSaveSheetUserWeights() {
  const queryClient = useQueryClient();

  return useMutation<void, Error, SheetUserWeightsDTOType>({
    mutationFn: saveSheetUserWeightsAction,
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries(["sheetUserWeights", variables.userId, variables.sheetId]);
    },
  });
}

Benefits of This Approach

  1. Type Safety
    • Both the client and server share the same DTO definitions, ensuring consistent data structures.
  2. Validation:
    • DTOs can validate incoming and outgoing data at runtime, catching errors early.
  3. Reusability:
    • DTOs can be reused across server actions, client-side hooks, and even database queries.
  4. Scalability:
    • As the application grows, DTOs provide a clear and maintainable way to manage data contracts.

When to Use DTOs

  • Complex Applications:
    • DTOs are especially useful in applications with complex data flows or multiple layers (e.g., client, server, database).
  • Shared Data Contracts:
    • When the same data structure is used across multiple parts of the application (e.g., client and server).
  • Validation Requirements:
    • When you need to validate data at runtime to ensure correctness.

Conclusion

Using DTOs as interfaces for server actions in Next.js is a powerful design pattern that improves type safety, validation, and maintainability. By defining DTOs with tools like zod or TypeScript interfaces, you can standardize data contracts across your application and ensure consistency between the client and server.

This article was generated mostly via my prompting of AI CoPilot; Image was generated via Claude.ai. I had to edit the result.

 
 

Adding Markdown (mdx) Support to NextJS with App Router

04 Jul

Markdown allows text content to be written with implicit formatting that nearly matches how one would write text without thought of formatting. A lot of web content is also simply text content; a lot of web content is built on the ReactJS library; and NextJS has become a popular framework, extending ReactJS. With that, I was motivated to use the Markdown content of my NextJS web app, but I had trouble simply relying on their documentation, so I thought I would document what I got working.

  1. Use Markdown for a text-heavy portion of a page of my web app, and
  2. To be able to have an entire page of content come from a single Markdown page

The former is a feature of NextJS to allow markdown content to be imported as a “component,” which can be rendered like any component in ReactJs.

The latter would rely NextJS’s App Router that assumes many URL paths for the app parallel the file/directory hierarchy of the app itself. So, merely placing a “page” file in the app’s directory will create a new URL path for the website. That means that you can create a “page.md” or “page.mdx” file with Markdown text content to define the content for that URL. .mdx files also allow JSX syntax so that React/Next components can be embedded within the Markdown. Read the rest of this entry »

 
 

Command line Clipboard for macOS, Cygwin, and Linux

25 Mar

I use the command line a lot, even though I am on a graphical user-interface (GUI) on Windows, macOS, or Linux. And since I’m lazy, I write a lot of scripts to perform repetitive tasks. I stumbled across macOS commands that allow command line programs to copy/paste between the clipboard that we’re so used to using.

macOS pbpaste and pbcopy

macOS has two commands, pbpaste and pbcopy, which “paste” from the pasteboard to stdout and copies from stdin to the pasteboard, respectively. Read the rest of this entry »

 

Bash recipe: Script call-stack

25 Mar
Bash logoHere’s a little recipe for printing a “call-stack” for Bash scripts. A call-stack is normally the list of nested function calls. This recipe will utilize the BASH_SOURCE, BASH_LINENO, and FUNCNAME environment variables, set by Bash during script executions, to display a call-stack of one script to another, excluding function calls; this will show only the invocations of one script to another. Read the rest of this entry »
 

ChatGPT: What is the common way to parse command line parameters with Rust?

04 Mar

Rust Programming Language logoIn Rust, the most common way to parse command-line parameters is by using the std::env::args function, which returns an iterator over the command-line arguments passed to the program. Read the rest of this entry »

 

Ruby RSpec FactoryBot traits and transient via ChatGPT

15 Dec

The following was edited together from a series of questions to ChatGPT on the topic. Currently, I cannot identify sources of the originating content. My role was to edit ChatGPT into the following.

FactoryBot

RSpec is a testing framework for the Ruby programming language, and FactoryBot (formerly known as Factory Girl) is a library for creating test data in Ruby. Together, these tools can be used to write unit-tests for a Ruby application.

In FactoryBot, a factory is a blueprint for creating test data objects. A factory can define various attributes of the objects it creates, such as the object’s type, the values of its attributes, and any associations it has with other objects.

Read the rest of this entry »
 
 

Frontend Security Recipe Checklist

03 Jan

Even as the number of frontend programming vulnerabilities grows continually, many are not difficult to combat; you simply need to remember to fortify your frontend security against them.

 
 

Catching the Technology Train, After I’d Disembarked

12 Jun

https://www.capitolcorridor.org/wp-content/uploads/2016/04/Capitol_Corridor_SanJoaquin.jpg

Staying away from programming for too long, things change faster than I’d ever expected. Even with a strong foundation, when things move ahead so quickly, too many things change and it isn’t as easy to hop back on the train, as I would have thought. Today, I am trying to catch up with technology train that is web development. How did I become so disconnected?

  1. My initial disinterest was that I wasn’t very interested. With web “development,” writing text-markup was not “programming” (it still isn’t). It has taken a couple of decades for web-programming to require the skills of true developers. But it is here, today. In the mean time, I’d left it behind and as a result, it left me behind.
  2. When I recently started looking at getting into web-development, the landscape was changing and evolving so quickly that it was hard to pin down which of the dozens of technologies to focus on. If I’d picked Perl, ColdFusion, Ruby, AngularJS, etc.… ”the list is endless” I’d be an expert in another obsolete technology. However, avoiding a pick, meant that I did not get my hands dirty and did not stay keen about the evolving landscape of web-development.
  3. What I never expected is that it isn’t just the languages and frameworks which have changed, it is also the technology infrastructure which has changed and, notably, this has changed development workflow.

The result is that, not only do I need to learn new languages and their nuances, I even need to learn a new workflow, a new way of doing programming. While I am not starting from scratch, a lot of my prior “expertise” is not directly applicable; so it feels like starting from scratch.

I could just stick to the old technologies that I am still expert at, but that isn’t cutting edge. Still I could do that. Then I wouldn’t have anything to be stressed about. And, that wouldn’t be so bad, now would it?

 
 

ReactJS: componentWillReceiveProps() is Dead! (*sniff*)

21 Apr

ReactJSThings evolve. ReactJS evolves. With version 16.3, there were several changes to the component life-cycle methods. In particular, componentWillReceiveProps is disappearing. In its place, they say, you can use the getDerivedStateFromProps static function. I found this a bit challenging, but I did find an interesting pattern when the component-state was dependent on fetched information.

I should mention that I had a specific goal to better encapsulate data within the component. While I could pass in all the needed data as properties, that would require the surrounding component know what to pass and how to get it. That shouldn’t necessarily be necessary; the component knows what it needs and how to retrieve it.

For example, say you have a component which accepts a phone number and displays the phone number and the state that it’s from. Certainly, you could write a simple component that accepts both pieces of information as properties.

<ShowPhoneLocation number="+12065551212" city="Seattle" />

Which might be implemented as:

class ShowPhoneLocation extends React.Component {
  render() {
    return (
      <div>{this.props.number} is from {this.props.city}</div>
    )
  } // render()
} // class ShowPhoneLocation

But, since the component should be able to infer the state from the phone number (by its area code), it shouldn’t be incumbent on its container to know what it is.

class ShowPhoneLocation extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    let location = getCityFromPhone(nextProps.number)
    return {
      city: location
    }
  }
  render() {
    return (
      <div>{this.props.number} is from {this.state.city}</div>
    )
  } // render()
} // class ShowPhoneLocation

That’s all well and good, but what if getCityFromPhone() has to call a web service? We don’t want getDerivedStateFromProps() to stall, waiting for a response. However, it is static and does not have a this reference to the object for which it is returning state; so an asynchronous fetch doesn’t know what object’s state to set. Instead, don’t wait for the result to save in the state, save the request’s Promise in the state and update the state, once the promise resolves.

function getCityFromPhone(number) {
  return fetch('http://saas.com/get/'+number+'/city') // Returns fetch promise
}
class ShowPhoneLocation extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    let location = getCityFromPhone(nextProps.number)
    return {
      city: location
    }
  }
  componentDidUpdate() {
    let location = this.state.city
    if (location instanceof Promise) {
      this.setState({ city: '...waiting...' })
      location.then(city => this.setState({ city: city }) )
        .catch(() => this.setState({ city: 'not found' }) )
    }
  }
  render() {
    return (
      <div>
        {this.props.number} is from {this.state.city instanceof Promise
         ? '...'
        : this.state.city}</div>
    )
  } // render()
} // class ShowPhoneLocation

In componentDidUpdate() you can define the completion handlers to set the object’s state, base on the returned information from the service.

It is a common pattern to perform a fetch in componentDidMount(). The problem is that there may not be enough information to perform the fetch, that early, or the information for the fetch changes after the component has been mounted.

I am going to miss componentWillReceiveProps()… without it, things become a bit more convoluted but it’s going the way of the Dodo.

 

Your Elevator Pitch is Important for YOU

20 Oct

It was StartupWeek 2017 in Seattle, recently. It was a time when anyone with a startup or an idea to start a business was roaming the city’s talks, gatherings, and events to learn how to become a success. Networking and chatting was a big part of the activity, so I got to hear a lot of people try to describe their ideas. I noticed that many of those early in their startup are not very clear about what they are trying to do or they take 20-minutes before they are… they have no elevator pitch.

What is an Elevator Pitch?

Can you encapsulate the essence of your startup idea so that you can express it in the time it takes to travel a few floors of an elevator? If you were to meet the person that could most help you with your startup, as you enter an elevator, can you clearly articulate and spark their interest before either of you exit the elevator?

An elevator pitch encapsulates a description of your startup, its meaning and purpose, in as few words as possible.

Why is An Elevator Pitch Important?

At every stage of the evolution of a startup, you will need help. Whether looking for employees, co-founders, investors, advisors, or customers, you will need to pique their interest quickly. You’ll want them to say, “I want to hear more!” You want them to invite you to follow them off the elevator.

In an elevator, you have a captive audience; but you will only hold their attention for so long. Of course this even more true outside an elevator where it is more difficult to hold their attention. So, it is important to keep your pitch succinct.

Why Else is an Elevator Pitch Important?

Okay, if you are already familiar with what an elevator pitch is, then I haven’t told you anything new. The real reason why it is important for you to create an elevator pitch is to help you crystallize your startup idea. The process of building and refining a pitch forces you to think very carefully about your idea. It forces you to narrow in on the essence of your idea… to identify the most salient elements of your idea. In that process, it might even help you set priorities for your startup.

Get into the habit of iterating your elevator pitch. As your startup lives, it evolves. It’s goals may change. A pitch should correspond to the changes that are occurring to the company. Regular updating of the elevator pitch might highlight changes that you did not anticipate and allow you to encourage or halt changes, as you see fit. It will certainly ensure that your message matches what you are building.

What Does an Elevator Pitch Look Like?

There are a lot of books, blogs, and advice (like my prior post) about what should be covered in a startup elevator pitch. At a minimum, you should identify what the problem is that you are trying to solve and how you are solving it.

How long should an elevator pitch be? Simply, as short as possible. Elevator rides can vary… you will have varying amounts of time to present your message. Start with a 3-minute pitch. Then work on shortening it to 2-minutes… 90-seconds… 60-seconds… 30-seconds! Ideally, you would be able to describe your company in a sentence or two.

Cutting words is not easy, but this exercise of paring down your message forces you to evaluate your company’s purpose. It is important to present a focused, achievable idea in the pitch message. If the idea is being lost as words are cut, then perhaps the company’s goals need to be simplified or scaled back. Or, you need to prioritize goals so that a message can focus on a narrower set of primary objectives.

Should you interest someone enough for them to want to hear more, be prepared to present longer descriptions. As the talks get longer, they begin to sound more like your full-fledged presentations. But, be prepared to work within any time-constraint you might be faced with. You can build out to 5-, 10-, 20-minute presentations. With longer pitches and presentations, you should create different versions targeted at different audiences, as necessary.

Eventually, you will have a range of pitches from a couple of sentences to a full-presentation at your fingertips… and on the tip of your tongue.

An Elevator Pitch Informs Others and Yourself

It is important to get your message across, quickly, as you meet people who might be helpful to your startup. Just as important, creating an elevator pitch will force you to identify and prioritize the “minimum viable product” of your idea.

  1. Research the elements of what goes into a startup elevator pitch
  2. Isolate the essence of your startup
  3. Articulate your startup in 3-minutes (or less)
  4. Further net out essential elements of your startup and your message for shorter versions of your pitch
  5. Longer presentation-pitches can fill the gaps between your elevator pitch and a full presentation.

Having a concise pitch means you had to identify the focus of your company. Continually adjusting your pitch to match your evolving startup keeps you in-tune with your venture. This, in itself, will improve your understanding of your company and allow you to sharpen its focus.

 
No Comments

Posted in Startup