<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Andrej Tlčina</title>
    <description>The latest articles on Forem by Andrej Tlčina (@_andi_).</description>
    <link>https://forem.com/_andi_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F889946%2F2b92858c-8802-4a6c-8cb0-078323e53b96.jpeg</url>
      <title>Forem: Andrej Tlčina</title>
      <link>https://forem.com/_andi_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/_andi_"/>
    <language>en</language>
    <item>
      <title>Building a Collaborative Todo App w/ live cursors using GraphQL subscriptions</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Sat, 03 Sep 2022 14:54:59 +0000</pubDate>
      <link>https://forem.com/_andi_/building-a-collaborative-todo-app-w-live-cursors-using-graphql-subscriptions-1j2h</link>
      <guid>https://forem.com/_andi_/building-a-collaborative-todo-app-w-live-cursors-using-graphql-subscriptions-1j2h</guid>
      <description>&lt;p&gt;Hello! I started a new project! I initially wanted to do a chat app, but then I realized, I would be just following tutorials, so I decided to build a collaborative real-time todo app, where each user can see the cursors of other users. My immediate thinking went to socket.io, but then I found &lt;a href="https://www.youtube.com/watch?v=E3NHd-PkLrQ" rel="noopener noreferrer"&gt;video&lt;/a&gt; by Jack Herrington. In this video, Jack used GraphQL subscriptions, which are (by my super scientific explanation) GraphQL WebSockets. When a subscription is defined on the server and the client subscribes to it, the user can get new data via a WebSocket connection. &lt;br&gt;
Once I did a little research, I realized I could build a real-time app with the help of GraphQl. Two things (real-time and GraphQL) I always wanted to try. &lt;br&gt;
So, here's a little preview of what I've built and a bit of explanation of how I did it 😉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ogktolotbnzt1ia52ix.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ogktolotbnzt1ia52ix.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By the way, I won't be talking about things like "what is GraphQL", because there's a lot of better sources for that. Also, no styles or react components, because I'm hoping you'll create your own components and styles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;
  
  
  Set-up
&lt;/h1&gt;

&lt;p&gt;For this project, I needed a server and client. In the empty directory, I created a server folder. In the server folder I created &lt;code&gt;src/index.tsx&lt;/code&gt;, then ran &lt;code&gt;npm init -y&lt;/code&gt; and downloaded a few things&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D @types/node @types/uuid nodemon ts-node typescript
yarn add graphql-yoga uuid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;nodemon is there for running the server, for that I also had to create a new script "dev": "nodemon" and &lt;code&gt;nodemon.json&lt;/code&gt; next to package.json. &lt;code&gt;nodemon.json&lt;/code&gt; holds configuration mine can be found &lt;a href="https://github.com/Attanox/intrepid/blob/main/server/nodemon.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this project I used MongoDB. You can get free DB following &lt;a href="https://www.prisma.io/blog/fullstack-remix-prisma-mongodb-1-7D0BfTXBmB6r#create-a-mongodb-instance" rel="noopener noreferrer"&gt;this link&lt;/a&gt;. To work with DB, I love to use prisma. You can set up prisma following &lt;a href="https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project/mongodb-typescript-mongodb" rel="noopener noreferrer"&gt;this link&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For client set-up we can use &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn create vite client --template react-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I also added &lt;code&gt;tailwind&lt;/code&gt; via &lt;a href="https://tailwindcss.com/docs/guides/vite" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can add really necessary packages on client via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @apollo/client graphql subscriptions-transport-ws throttle-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In the previous paragraph, I wrote &lt;strong&gt;really necessary packages&lt;/strong&gt;, because I used packages like &lt;code&gt;@react/dialog&lt;/code&gt;, which are cool but not necessary, and you don't even need tailwind, if you like other full-fledged UI libraries, for instance, Chakra UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Initialize Server-Client connection
&lt;/h1&gt;

&lt;p&gt;Obviously, we want the server and client to "talk" to each other. For that, we have to initialize the server and then connect the client to it.&lt;br&gt;
In &lt;code&gt;server/index.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { GraphQLServer, PubSub } from "graphql-yoga";
import { PrismaClient } from "@prisma/client";

const pubsub = new PubSub();
const prisma = new PrismaClient();

export interface Context {
  pubsub: PubSub;
  prisma: PrismaClient;
}

const typeDefs = `
  type Query {
    hello: String!
  }
`

const resolvers = {
  Query: {
    hello: () =&amp;gt; "world",
  },
}

const server = new GraphQLServer({
  typeDefs,
  resolvers,
  context: { pubsub, prisma } as Context,
});

server.start(({ port }) =&amp;gt; {
  console.log(`Server on http://localhost:${port}/`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running &lt;code&gt;yarn dev&lt;/code&gt; we should get a log with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server on http://localhost:4000/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means server is running, and after visiting &lt;code&gt;http://localhost:4000/&lt;/code&gt;, there's a GraphiQL interface, where we can test our queries and whatnot.&lt;/p&gt;

&lt;p&gt;To connect client to server I like to create a file &lt;code&gt;app/App.tsx&lt;/code&gt;, where I'll have something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

import WithApollo from "./withApollo";

function App() {
  return (
    &amp;lt;WithApollo&amp;gt;
      // other providers and components
    &amp;lt;/WithApollo&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see me importing a &lt;code&gt;WithApollo&lt;/code&gt; provider. Code for that is in &lt;code&gt;app/withApollo.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";

const link = new WebSocketLink({
  uri: `ws://localhost:4000/`,
  options: {
    reconnect: true,
  },
});

const client = new ApolloClient({
  link,
  uri: "http://localhost:4000/",
  cache: new InMemoryCache(),
});

const WithApollo = (props: React.PropsWithChildren&amp;lt;{}&amp;gt;) =&amp;gt; {
  const { children } = props;

  return &amp;lt;ApolloProvider client={client}&amp;gt;{children}&amp;lt;/ApolloProvider&amp;gt;;
};

export default WithApollo;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see a &lt;code&gt;WebSocketLink&lt;/code&gt;, which listens to websocket traffic and &lt;code&gt;ApolloClient&lt;/code&gt; which helps us with creating GraphQL request to server. Speaking of let's make one in &lt;code&gt;app/App.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql, useQuery } from '@apollo/client';
const GET_HELLO = gql`
  query {
    hello
  }
`;

function App() {
  const { data } = useQuery(GET_HELLO);

  console.log(data) 

  return (
    &amp;lt;WithApollo&amp;gt;
      // other providers and components
    &amp;lt;/WithApollo&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In console, you should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "hello" : "world"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Live cursors
&lt;/h2&gt;

&lt;p&gt;When adding a new feature on the server I usually first define types in the schema and then I complete the feature in the resolver. In the schema, I added&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const typeDefs = `
  type Cursor {
    id: ID!
    name: String!
    x: Float!
    y: Float!
  }
  input CursorInput {
    id: ID!
    name: String!
    x: Float!
    y: Float!
  }
  type Query {
    cursors: [Cursor!]
  }
  type Mutation {
    updateCursor(c: CursorInput!): ID!
    deleteCursor(id: ID!): ID!
  }
  type Subscription {
    cursors(c: CursorInput): [Cursor!]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no &lt;code&gt;addCursor&lt;/code&gt; mutation, because that's done via subscription, speaking of, to resolver, I added&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import type { Context } from "./server";

interface Cursor {
  id: string;
  name: string;
  x: number;
  y: number;
}

const cursors: { [id: string]: Cursor } = {};

const cursorsSubscribers: (() =&amp;gt; void)[] = [];
const onCursorsUpdates = (fn: () =&amp;gt; void) =&amp;gt; cursorsSubscribers.push(fn);
const spreadCursors = () =&amp;gt; cursorsSubscribers.forEach((fn) =&amp;gt; fn());

const generateChannelID = () =&amp;gt; Math.random().toString(36).slice(2, 15);

const resolvers = {
  Query: {
    cursors: () =&amp;gt; [...Object.values(cursors)],
  },
  Mutation: {
    updateCursor: (_: any, args: { c: Cursor }) =&amp;gt; {
      if (!args?.c?.id) return "";

      cursors[args.c.id] = {
        ...cursors[args.c.id],
        ...args.c,
      };
      spreadCursors();
      return args.c.id;
    },
    deleteCursor: (_: any, args: { id: string }) =&amp;gt; {
      if (!args?.id) return "";

      delete cursors[args.id];
      spreadCursors();

      return args.id;
    },
  },
  Subscription: {
    cursors: {
      subscribe: (
        _: any,
        args: { c: Cursor },
        { pubsub }: { pubsub: Context["pubsub"] }
      ) =&amp;gt; {
        const channel = generateChannelID();

        if (!args.c.id) return;

        cursors[args.c.id] = { ...args.c };

        onCursorsUpdates(() =&amp;gt;
          pubsub.publish(channel, { cursors: [...Object.values(cursors)] })
        );
        setTimeout(
          () =&amp;gt;
            pubsub.publish(channel, {
              cursors: [...Object.values(cursors)],
            }),
          0
        );

        return pubsub.asyncIterator(channel);
      },
    },

  },
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;There's some &lt;code&gt;any&lt;/code&gt; types, because I did not set up codegen, and also I do not use that first argument (hence the &lt;code&gt;_&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the server setup. The subscription is listening for when the new cursor is added through the client. &lt;code&gt;updateCursor&lt;/code&gt; updates the position of the cursor, when the user moves the mouse and lastly, the cursor is deleted when a client leaves the app (closes the tab).&lt;/p&gt;

&lt;p&gt;Let's add these events to client, but first I want to bring up an article that helped me understand animating cursors (I also copied their cursor code). You can read it &lt;a href="https://liveblocks.io/blog/how-to-animate-multiplayer-cursors?utm_campaign=This%20Week%20In%20React&amp;amp;utm_medium=email&amp;amp;utm_source=Revue%20newsletter" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code for cursor is here&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/Cursor.tsx

function CursorSvg({ color }: { color: string }) {
  return (
    &amp;lt;svg
      width={CURSOR_SIZE}
      height={CURSOR_SIZE}
      viewBox="0 0 24 36"
      fill="none"
    &amp;gt;
      &amp;lt;path
        fill={color}
        d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
      /&amp;gt;
    &amp;lt;/svg&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, I created a &lt;strong&gt;collab area&lt;/strong&gt;, where all cursors, including the current user's cursor, will be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//  components/CollabArea.tsx

interface Cursor {
  id: string;
  name: string;
  x: number;
  y: number;
}

interface Cursors {
  cursors: Cursor[];
}

const CURSORS = gql`
  subscription ($cursor: CursorInput!) {
    cursors(c: $cursor) {
      id
      name
      x
      y
    }
  }
`;

const CollabArea = (props: React.PropsWithChildren&amp;lt;{}&amp;gt;) =&amp;gt; {
  const { children } = props;

  const [currentUser, setCurrentUser] = React.useState({ id: "", name: "" });

  const { data } = useSubscription&amp;lt;Cursors&amp;gt;(CURSORS, {
    variables: {
      cursor: { id: currentUser.id, name: currentUser.name, x: 0, y: 0 },
    },
    shouldResubscribe: !!currentUser.id,
    skip: !currentUser.id,
  });

  return (
    &amp;lt;React.Fragment&amp;gt;
      /* this can be a simple form, where users will type their names, the ID can be done with simple Date.now().toString(), at least for now (probably not in production) */
      &amp;lt;UserEnter setCurrentUser={setCurrentUser} /&amp;gt;

      {data?.cursors.map((c) =&amp;gt; {
        const posX = c.x * window.innerWidth;
        const posY = c.y * window.innerHeight;

        return (
          &amp;lt;Cursor
            key={c.id}
            id={c.id}
            name={c.name}
            x={posX}
            y={posY}
          /&amp;gt;
        );
      })}

      {children}
    &amp;lt;/React.Fragment&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CollabArea&lt;/code&gt; is a wrapper containing all cursors. With the help of &lt;code&gt;useSubscription&lt;/code&gt; I could subscribe to the server and now every time, there will be a change on the server, involving cursors, the server will send the updated data to the client. To display the cursor I've made a component called (you guessed it) &lt;code&gt;Cursor&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/Cursor.tsx

import { motion, useMotionValue } from "framer-motion";

const CURSOR_SIZE = 30;

const Cursor = (
  { id, name, x, y, current } = {
    id: "0",
    name: "",
    x: 0,
    y: 0,
  }
) =&amp;gt; {
  const posX = useMotionValue(0);
  const posY = useMotionValue(0);

  React.useEffect(() =&amp;gt; {
    posX.set(x - CURSOR_SIZE / 2);
  }, [x]);

  React.useEffect(() =&amp;gt; {
    posY.set(y - CURSOR_SIZE / 2);
  }, [y]);

  /* you can get color however you like, even randomly */
  const color = getColor(name);

  return (
    &amp;lt;motion.div
      style={{
        top: "0",
        left: "0",
        position: "absolute",
        zIndex: "999999999",
        pointerEvents: "none",
        userSelect: "none",
        transformOrigin: "left",
      }}
      initial={{ x: posX.get(), y: posY.get() }}
      animate={{ x: posX.get(), y: posY.get() }}
      transition={{
        type: "spring",
        damping: 30,
        mass: 0.8,
        stiffness: 350,
      }}
    &amp;gt;
      &amp;lt;CursorSvg color={color} /&amp;gt;
    &amp;lt;/motion.div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I have these cursors changing their position, when &lt;code&gt;x&lt;/code&gt; or &lt;code&gt;y&lt;/code&gt; coordinates change, but they're not changing 😅. All the cursors are now in the position [0, 0]. To update their positions I'll create a hook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/CollabArea.tsx

import { throttle } from "throttle-typescript";

const UPDATE_CURSOR = gql`
  mutation ($cursor: CursorInput!) {
    updateCursor(c: $cursor)
  }
`;

const useUpdateCursor = (id: string, name: string) =&amp;gt; {
  const [updateCursor] = useMutation(UPDATE_CURSOR);

  const [visible, setVisible] = React.useState(false);

  const hideCursor = () =&amp;gt; setVisible(false);
  const showCursor = () =&amp;gt; setVisible(true);

  const onMouseMove = (e: MouseEvent) =&amp;gt; {
    const posX = e.clientX;
    const posY = e.clientY;

    const serverPosition = {
      x: posX / window.innerWidth,
      y: posY / window.innerHeight,
    };

    updateCursor({
      variables: {
        cursor: {
          id,
          name,
          ...serverPosition,
        },
      },
    });
  };
  const onThrottledMouseMove = React.useCallback(throttle(onMouseMove, 30), [
    id,
  ]);

  React.useEffect(() =&amp;gt; {
    document.addEventListener("mousemove", onThrottledMouseMove);
    document.addEventListener("mouseleave", hideCursor);
    document.addEventListener("mouseenter", showCursor);

    return () =&amp;gt; {
      document.removeEventListener("mousemove", onThrottledMouseMove);
      document.removeEventListener("mouseleave", hideCursor);
      document.removeEventListener("mouseenter", showCursor);
    };
  }, [id]);

  return { visible };
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I'm defining mutation and on each cursor move, I call it. I added an extra state for when the user leaves the collab area via &lt;code&gt;visible&lt;/code&gt; state. To use it just add it to &lt;code&gt;CollabArea&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const CollabArea = (props: React.PropsWithChildren&amp;lt;{}&amp;gt;) =&amp;gt; {
  ...

  const { visible } = useUpdateCursor(currentUser.id, currentUser.name);

  ...
  return (
    ...
    {data?.cursors.map((c) =&amp;gt; {
        const posX = c.x * window.innerWidth;
        const posY = c.y * window.innerHeight;
        /* check if cursor belongs to current user */
        const isCurrent = currentUser.id === c.id;
        /* if it does and it's not visible don't display the cursor */
        if (isCurrent &amp;amp;&amp;amp; !visible) return null;

        return (
          &amp;lt;Cursor
            key={c.id}
            id={c.id}
            name={c.name}
            current={isCurrent}
            x={posX}
            y={posY}
          /&amp;gt;
        );
      })}

  )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can see the cursor updates when moving the mouse.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The movement will be a bit delayed. If you want your cursor to be "real-time" make a hook or use the state to achieve that, I kind of don't mind.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last thing is to remove the cursor on the server when the user closes the tab. For that, I created another hook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const DELETE_CURSOR = gql`
  mutation ($id: ID!) {
    deleteCursor(id: $id)
  }
`;

const useRemoveUser = (userID: string) =&amp;gt; {
  const [deleteCursor] = useMutation(DELETE_CURSOR);

  const onUserLeaving = async (event: Event) =&amp;gt; {
    event.preventDefault();

    await deleteCursor({
      variables: {
        id: userID,
      },
    });

    return true;
  };

  React.useEffect(() =&amp;gt; {
    window.onunload = onUserLeaving;

    return () =&amp;gt; {
      window.onunload = null;
    };
  }, [userID]);

  return null;
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, just add it to the &lt;code&gt;CollabArea&lt;/code&gt; and we're done with the live cursors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const CollabArea = (props: React.PropsWithChildren&amp;lt;{}&amp;gt;) =&amp;gt; {
  ...
  useRemoveUser(currentUser.id);
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Todos
&lt;/h2&gt;

&lt;p&gt;So, todos are a little easier, mainly because we'll be implementing CRUD functionality. The todos will be saved in the database, so let's create a model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Todo {
  id           String   @id @default(auto()) @map("_id") @db.ObjectId
  text         String
  is_completed Boolean
  order        Int
  created_at   DateTime @default(now())
  updated_at   DateTime @updatedAt
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's continue with the schema. I kind of enjoy making these function definitions to get the bird's-eye view of the API or the server functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const typeDefs = `
  type Todo {
    id: ID!
    text: String!
    is_completed: Boolean!
    order: Int!
  }
  type Query {
    ...
    messages: [Message!]
  }
  type Mutation {
    ...
    addTodo(text: String!): ID!
    updateTodo(id: ID!, is_completed: Boolean!): ID!
    deleteTodo(id: ID!): ID!
  }
  type Subscription {
    ...
    todos: [Todo!]
  }
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The names are hopefully self-explanatory. Again, the Read portion of the app will be done via Subscription or Query. Let's first subscribe to todos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const todosSubscribers: (() =&amp;gt; Promise&amp;lt;boolean&amp;gt;)[] = [];
const onTodosUpdates = (fn: () =&amp;gt; Promise&amp;lt;boolean&amp;gt;) =&amp;gt;
  todosSubscribers.push(fn);
const spreadTodos = () =&amp;gt; todosSubscribers.forEach(async (fn) =&amp;gt; await fn());

const resolvers = {
  Query: {
    ...
    todos: async (
      _: any,
      _args: any,
      { prisma }: { prisma: Context["prisma"] }
    ) =&amp;gt; await prisma.todo.findMany(),
  },
  Mutation: {
    ...
  },
  Subscription: {
    ...
    todos: {
      subscribe: async (_: any, _args: any, { pubsub, prisma }: Context) =&amp;gt; {
        const channel = generateChannelID();

        // using function here, b/c if we would just get the todos, the result would be stale
        const getTodos = async () =&amp;gt; await prisma.todo.findMany();

        onTodosUpdates(async () =&amp;gt;
          pubsub.publish(channel, { todos: await getTodos() })
        );
        setTimeout(
          async () =&amp;gt; pubsub.publish(channel, { todos: await getTodos() }),
          0
        );

        return pubsub.asyncIterator(channel);
      },
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, in components, I created &lt;code&gt;TodosList&lt;/code&gt; component, which displays all the todos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/TodosList.tsx

interface Todo {
  id: string;
  text: string;
  is_completed: boolean;
}

interface TodosQuery {
  todos: Todo[];
}

const GET_TODOS = gql`
  subscription {
    todos {
      id
      text
      is_completed
    }
  }
`;

const Todos = () =&amp;gt; {
  const { data } = useSubscription&amp;lt;TodosQuery&amp;gt;(GET_TODOS);

  return (
    // map over data.todos and display them
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, there are no todos, unless the database was seeded. So, the next step will be adding an add todo functionality in resolvers.&lt;/p&gt;

&lt;p&gt;const resolvers = {&lt;br&gt;
  ...&lt;br&gt;
  Mutation: {&lt;br&gt;
    addTodo: async (_: any, { text }: { text: string }, ctx: Context) =&amp;gt; {&lt;br&gt;
      const todos = await ctx.prisma.todo.findMany();&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const created = await ctx.prisma.todo.create({
    data: {
      text,
      is_completed: false,
      order: todos.length,
    },
  });

  spreadTodos();
  return created.id;
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;},&lt;br&gt;
  ...&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;The adding of todo is done via Prisma client and all we have to pass in is a text of the todo. On the client we define mutation like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ADD_TODO = gql`
  mutation ($text: String!) {
    addTodo(text: $text)
  }
`;

const [postMessage] = useMutation(ADD_TODO);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and call it like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postMessage({
  variables: { text: some_text },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delete and update functionality is pretty similar so I'll show them both at once&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const resolvers = {
  ...
  Mutation: {
    ...
    updateTodo: async (
      _: any,
      { id, is_completed }: { id: string; is_completed: boolean },
      ctx: Context
    ) =&amp;gt; {
      await ctx.prisma.todo.update({
        where: {
          id,
        },
        data: {
          is_completed,
        },
      });

      spreadTodos();
      return id;
    },
    deleteTodo: async (_: any, { id }: { id: string }, ctx: Context) =&amp;gt; {
      await ctx.prisma.todo.delete({
        where: {
          id,
        },
      });

      spreadTodos();
      return id;
    },
  },
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the client we define mutations as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const UPDATE_TODO = gql`
  mutation ($id: ID!, $is_completed: Boolean!) {
    updateTodo(id: $id, is_completed: $is_completed)
  }
`;

const DELETE_TODO = gql`
  mutation ($id: ID!) {
    deleteTodo(id: $id)
  }
`;

const [updateTodo] = useMutation(UPDATE_TODO);
const [deleteTodo] = useMutation(DELETE_TODO);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling of mutations can be something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data.todos.map(({ id, text, is_completed }) =&amp;gt; (
...
&amp;lt;label&amp;gt;
   &amp;lt;input
     onChange={async (e) =&amp;gt;
       await updateTodo({
         variables: {
           id,
           is_completed: e.target.checked,
         },
       })
     }
     checked={is_completed}
     type="checkbox"
     className="checkbox"
   /&amp;gt;
&amp;lt;/label&amp;gt;
...
&amp;lt;button
  onClick={async () =&amp;gt;
    await deleteTodo({
      variables: {
        id,
      },
    })
  }
  className="btn btn-circle btn-outline btn-error"
&amp;gt;
  &amp;lt;CoolIcon /&amp;gt;
&amp;lt;/button&amp;gt;
...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's about it for Todos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was over how I implemented live cursors and todo app in GraphQL. The functionality can be, of course, extended by updating/deleting more todos at once, changing the order of todos, and adding codegen for better DX. I also added a chat feature.&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href="https://github.com/Attanox/intrepid" rel="noopener noreferrer"&gt;github&lt;/a&gt; for the whole project, styles, and whatnot.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>webdev</category>
      <category>graphql</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building Training Plan builder: Dealing w/ complex forms in Remix</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Sun, 07 Aug 2022 18:09:00 +0000</pubDate>
      <link>https://forem.com/_andi_/building-training-plan-builder-dealing-w-complex-forms-in-remix-3ekc</link>
      <guid>https://forem.com/_andi_/building-training-plan-builder-dealing-w-complex-forms-in-remix-3ekc</guid>
      <description>&lt;p&gt;Hello! Continuing this mini-series I'd like to share with you how I dealt with complex forms in Remix. You see, I'm creating this training plan builder, that currently looks like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tC31BQo0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9bsfs4rpe980zqkflw8.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tC31BQo0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9bsfs4rpe980zqkflw8.PNG" alt="Image description" width="880" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Remix expects you to process data, sent to the backend, via &lt;code&gt;actions&lt;/code&gt;. It's pretty cool when you're dealing with simple forms, but here I have these columns and each column has multiple exercises, so it'd be great if I could group the data somehow. &lt;/p&gt;

&lt;p&gt;The first idea was to deal with it as they have it in &lt;a href="https://remix.run/docs/en/v1/tutorials/blog#actions"&gt;docs&lt;/a&gt;, so something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const action = async ({ request }) =&amp;gt; {
  const formData = await request.formData();

  const title = formData.get("title");
  const slug = formData.get("slug");
  const markdown = formData.get("markdown");
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way I would somehow call &lt;code&gt;Object.entries&lt;/code&gt; on formData while giving the input names like &lt;code&gt;col-1-ex-1&lt;/code&gt;. You can imagine how tedious that was/would be. So, I searched for a solution and I found a pretty clever one with the help of the &lt;code&gt;qs&lt;/code&gt; (querystring) package. &lt;/p&gt;

&lt;p&gt;The package ensures I can give each of the exercise inputs names like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;phases[${idx}][${exercise.id}][title]&lt;/li&gt;
&lt;li&gt;phases[${idx}][${exercise.id}][sets]&lt;/li&gt;
&lt;li&gt;phases[${idx}][${exercise.id}][reps]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;which, after parsing the request with this snippet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const action: ActionFunction = async ({ request }) =&amp;gt; {
  const text = await request.text();
  const parsed = qs.parse(text) as YourCustomType

  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives the following structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;phases: [
  {
     exerciseId: { 
        sets: number,
        reps: number,
        title: string,
     },
     exerciseId2: { 
        sets: number,
        reps: number,
        title: string,
     }
  },
  {
     exerciseId3: { 
        sets: number,
        reps: number,
        title: string,
     }
  },
  {
     exerciseId4: { 
        sets: number,
        reps: number,
        title: string,
     },
     exerciseId5: { 
        sets: number,
        reps: number,
        title: string,
     }
  },
] // 3 objects represent 3 columns (phases) in the app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure is much easier to work with. And the best thing is that I got this structure using only two lines of code 😅 &lt;/p&gt;

&lt;p&gt;Thanks for reading and hope this helps someone! Take care 😉&lt;/p&gt;

&lt;p&gt;Source code -&amp;gt; &lt;a href="https://github.com/Attanox/strongion"&gt;Strongion&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Original thread -&amp;gt; &lt;a href="https://github.com/remix-run/remix/discussions/1541"&gt;github discussion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>react</category>
      <category>todayilearned</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building Training Plan builder: DnD &amp; Autocomplete</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Sun, 31 Jul 2022 14:27:25 +0000</pubDate>
      <link>https://forem.com/_andi_/building-training-plan-builder-dnd-autocomplete-m2l</link>
      <guid>https://forem.com/_andi_/building-training-plan-builder-dnd-autocomplete-m2l</guid>
      <description>&lt;p&gt;Hello! In the previous &lt;a href="https://dev.to/_andi_/building-training-plan-builder-introduction-c93"&gt;article&lt;/a&gt; I promised to talk about something more interesting. I was always curious about Drag and Drop functionality and I wanted to build something, which is going to use it as a feature. So, I came up with an app that helps you build a training plan by looking up different exercises. A plan consists of multiple phases, displayed as rectangle columns. I made up a scenario where the user will search for exercise. Upon selecting one, it will be appended into the special non-phase column. Users can then, drag this exercise from the non-phase column to any other. Exercises will be displayed as simple rectangles with a name, a button for removing a given exercise, and two inputs for filling out sets and reps. So let's start building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autocomplete
&lt;/h2&gt;

&lt;p&gt;I'd like to start things off by first creating autocomplete component, so we get the exercise options. For the component, I'll be using a library called &lt;a href="https://www.downshift-js.com/"&gt;Downshift&lt;/a&gt;. It is a headless UI component, which means I'll have all the needed functionality and state management, but no UI, which is a great pattern. Sometimes these component libraries can be too rigid that you can't customize them, this however gives you as much freedom as you want. I pretty much looked at the docs and followed along. The stuff that I added was fetching data from an external &lt;a href="https://wger.de/en/software/api"&gt;API&lt;/a&gt;. For that, I used &lt;code&gt;debounce&lt;/code&gt;, so we don't fetch on every key press, but after some time of inactivity. However there's a catch with debounce in React, you have to use it in useCallback (more on that &lt;a href="https://dmitripavlutin.com/react-throttle-debounce/"&gt;here&lt;/a&gt;). Also, when there are no exercises found I display the "No results" option in the list of options. The component will look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const NO_OPTIONS_OPT = {
  value: "NO_OPTIONS",
  data: {
    id: 0,
    name: "NO_OPTIONS",
    category: "",
    image: null,
    image_thumbnail: null,
  },
  info: { sets: 0, reps: 0 },
};

const getExerciseFilter =
  (inputValue: string | undefined) =&amp;gt; (item: ExerciseSuggestion) =&amp;gt; {
    return !inputValue || item.value.includes(inputValue);
  };

const AutoComplete = (props: {
  setSearchedExercises: (r: ExerciseSuggestion[]) =&amp;gt; void;
}) =&amp;gt; {
  const { setSearchedExercises } = props;

  const [items, setItems] = React.useState&amp;lt;ExerciseSuggestion[]&amp;gt;([]);

  const onInputValueChange = React.useCallback(
    debounce(
      async ({ inputValue }: UseComboboxStateChange&amp;lt;ExerciseSuggestion&amp;gt;) =&amp;gt; {
        const { suggestions } = await searchExercises(inputValue || "");

        if (!suggestions || !suggestions.length) {
          setItems([NO_OPTIONS_OPT]);
        } else {
          const filteredExercises = suggestions.filter(
            getExerciseFilter(inputValue)
          );
          setItems(filteredExercises);
        }
      },
      1000
    ),
    []
  );

  const {
    isOpen,
    getLabelProps,
    getMenuProps,
    getInputProps,
    getComboboxProps,
    highlightedIndex,
    getItemProps,
    selectedItem,
  } = useCombobox({
    onInputValueChange,
    items,
    itemToString(item) {
      return item ? item.value : "";
    },
    onSelectedItemChange(changes) {
      const exercise = changes.selectedItem;
      if (exercise) setSearchedExercises([exercise]);
    },
  });

  return (
    &amp;lt;div
      {...getComboboxProps()}
      className="form-control relative w-72 flex flex-col gap-1"
    &amp;gt;
      &amp;lt;label {...getLabelProps()} className="label label-text"&amp;gt;
        Search for exercise
      &amp;lt;/label&amp;gt;
      &amp;lt;input {...getInputProps()} className="input input-bordered w-full" /&amp;gt;
      &amp;lt;ul
        {...getMenuProps()}
        className="absolute top-24 w-72 bg-white shadow-md max-h-80 overflow-y-auto"
      &amp;gt;
        {isOpen
          ? items.map((item, index) =&amp;gt; {
              if (item.value === NO_OPTIONS_OPT.value) {
                return &amp;lt;li className="w-full"&amp;gt;No results...&amp;lt;/li&amp;gt;;
              }

              return (
                &amp;lt;li
                  key={item.value}
                  className="w-full cursor-pointer"
                  {...getItemProps({
                    key: item.value,
                    index,
                    item,
                    style: {
                      backgroundColor:
                        highlightedIndex === index ? "lightgray" : "white",
                      fontWeight: selectedItem === item ? "bold" : "normal",
                    },
                  })}
                &amp;gt;
                  {item.value}
                &amp;lt;/li&amp;gt;
              );
            })
          : null}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default AutoComplete;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component has "a lot of state" and Remix will try to render it on the server which will cause errors/warnings of mismatching ids. To avoid that, we have to save the file as &lt;code&gt;AutoComplete.client.tsx&lt;/code&gt;. This still won't fix the problem, because now the component will be rendered as undefined, to render it only on the client we have to install a package called &lt;code&gt;remix-utils&lt;/code&gt;. This package will provide us with a &lt;code&gt;ClientOnly&lt;/code&gt; utility, which will make sure the component gets rendered only on the client. So, we'll use &lt;code&gt;AutoComplete&lt;/code&gt; component like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ClientOnly fallback={&amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;}&amp;gt;
  {() =&amp;gt; &amp;lt;AutoComplete setSearchedExercises={...} /&amp;gt;}
&amp;lt;/ClientOnly&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Drag and Drop
&lt;/h2&gt;

&lt;p&gt;For Drag and Drop I used the component library &lt;a href="https://github.com/atlassian/react-beautiful-dnd"&gt;beautiful-dnd&lt;/a&gt;. I was sold on the fact that it gives me the exact functionality I needed and it's headless. &lt;br&gt;
The library gives us three main elements&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DragDropContext (context holding all of the values)&lt;/li&gt;
&lt;li&gt;Droppable (the container holding draggable elements)&lt;/li&gt;
&lt;li&gt;Draggable (the element user drags)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The general structure when working with beautiful-dnd will look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DragDropContext onDragEnd={...}&amp;gt;
 &amp;lt;Droppable&amp;gt;
   &amp;lt;Draggable /&amp;gt;
   &amp;lt;Draggable /&amp;gt;
   ...
 &amp;lt;/Droppable&amp;gt;
 &amp;lt;Droppable&amp;gt;
   &amp;lt;Draggable /&amp;gt;
   &amp;lt;Draggable /&amp;gt;
   ...
 &amp;lt;/Droppable&amp;gt;
 ...
&amp;lt;/DragDropContext&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For state I'm going to keep 2 state variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;elements of phase columns&lt;/li&gt;
&lt;li&gt;elements of search column
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const getItems = (phases: TPhases) =&amp;gt; {
  const result = [];
  for (let index = 0; index &amp;lt; phases.length; index++) {
    const phase = phases[index];
    const exercises = phase.exercises.map((el) =&amp;gt; ({
      name: el.name,
      id: el.id,
      info: { reps: el.exerciseData.reps, sets: el.exerciseData.sets },
    }));
    result.push([...exercises]);
  }
  return result;
};

const [phases, setPhases] = React.useState&amp;lt;DndExercise[][]&amp;gt;(
    getItems(initialPhases)
  );

const [searchedExercises, setSearchedExercises] = React.useState&amp;lt;
    DndExercise[]
  &amp;gt;([]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can also update &lt;code&gt;Autocomplete&lt;/code&gt; component by passing a props which will set searchedExercises&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;AutoComplete
  setSearchedExercises={(e) =&amp;gt; {
  const newlyAdded: DndExercise[] = [
    {
       name: e[0].value,
       id: String(e[0].data.id),
       info: { sets: 0, reps: 0 },
    },
  ];
  setSearchedExercises((prevStat) =&amp;gt; [...prevStat, ...newlyAdded]);
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, each &lt;code&gt;Droppable&lt;/code&gt; should have an id which is going to help with moving elements. So, we can rewrite the general code as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DragDropContext onDragEnd={...}&amp;gt;
 {
  phases.map((phase, idx) =&amp;gt; {
    return (
      &amp;lt;Droppable id={idx}&amp;gt;
        {phase.exercises.map(e =&amp;gt; {
          return &amp;lt;Draggable&amp;gt;some content&amp;lt;/Draggable&amp;gt;
        }
      &amp;lt;/Droppable&amp;gt;
    )
  }
 }
 &amp;lt;Droppable id={phases.length}&amp;gt;
    {searchExercises.map(e =&amp;gt; {
      return &amp;lt;Draggable&amp;gt;some content&amp;lt;/Draggable&amp;gt;
    }
 &amp;lt;/Droppable&amp;gt;
&amp;lt;/DragDropContext&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, for the hardest part, moving elements. The &lt;code&gt;onDragEnd&lt;/code&gt; gives you the &lt;code&gt;result&lt;/code&gt; object as an argument that holds important things like source ID (from which column we're dragging out) and destination ID (into which column we're dragging to).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const onDragEnd = (result: DropResult) =&amp;gt; {
    const { source, destination } = result;

    // dropped outside the list
    if (!destination) {
      return;
    }
    const sInd = +source.droppableId;
    const dInd = +destination.droppableId

    // move and reorder elements
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For moving and reordering elements I'm using functions you can find in the beautiful-dnd &lt;a href="https://codesandbox.io/s/-w5szl"&gt;examples&lt;/a&gt;. You can look at how they work, they're quite simple. I used them as these black box functions which need some input and return some output. &lt;br&gt;
There are a couple of scenarios that can happen. I made a little diagram, which will hopefully explain it better than a paragraph of text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6ClUu2R1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fe3q563nrpzr14bkfdte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6ClUu2R1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fe3q563nrpzr14bkfdte.png" alt="Image description" width="880" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const onDragEnd = (result: DropResult) =&amp;gt; {
    const { source, destination } = result;

    // dropped outside the list
    if (!destination) {
      return;
    }
    const sInd = +source.droppableId;
    const dInd = +destination.droppableId;

    if (sInd === dInd) {
      if (sInd === phases.length) {
        const newItems = reorder(
          searchedExercises,
          source.index,
          destination.index
        );
        setSearchedExercises(newItems);
      } else {
        const newItems = reorder(phases[sInd], source.index, destination.index);
        const newState = [...phases];
        newState[sInd] = newItems;
        setPhases(newState);
      }
    } else {
      if (sInd === phases.length) {
        const result = move(
          searchedExercises,
          phases[dInd],
          source,
          destination
        );
        const newState = [...phases];
        newState[dInd] = result[dInd];
        setPhases(newState);
        const newSearchedExercises = result[sInd];
        setSearchedExercises(newSearchedExercises);
      } else if (dInd === phases.length) {
        const result = move(
          phases[sInd],
          searchedExercises,
          source,
          destination
        );
        const newState = [...phases];
        newState[sInd] = result[sInd];
        setPhases(newState);
        const newSearchedExercises = result[dInd];
        setSearchedExercises(newSearchedExercises);
      } else {
        const result = move(phases[sInd], phases[dInd], source, destination);
        const newState = [...phases];
        newState[sInd] = result[sInd];
        newState[dInd] = result[dInd];
        setPhases(newState);
      }
    }
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm storing all the phase values in state variables. Meaning phase attributes like name, description, but also phase exercise attributes. I mentioned each displayed exercise will have two inputs, so the user can set sets and reps. I made a function for changing these values in state variables. I hooked it up on blur. Depending on column's id it changes the contents of a particular state variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const changeSetsAndReps = (
    value: number,
    what: "sets" | "reps",
    whichCol: number,
    whichRow: number
  ) =&amp;gt; {
    if (whichCol === phases.length) {
      const copied = [...searchedExercises];
      copied[whichRow] = {
        ...copied[whichRow],
        info: {
          ...copied[whichRow]["info"],
          [what]: value,
        },
      };
      setSearchedExercises(copied);
    } else {
      const copied = [...phases];
      copied[whichCol][whichRow] = {
        ...copied[whichCol][whichRow],
        info: {
          ...copied[whichCol][whichRow]["info"],
          [what]: value,
        },
      };
      setPhases(copied);
    }
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, I just implemented remove functionality, depending on if the displayed exercise is in the search column or phase column I call either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const removeSearched = (exerciseIdx: number) =&amp;gt; {
    const newSearched = [...searchedExercises];

    newSearched.splice(exerciseIdx, 1);

    setSearchedExercises(newSearched);
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
  const removeExercise = (phaseIdx: number, exerciseIdx: number) =&amp;gt; {
    const newPhases = [...phases];

    newPhases[phaseIdx].splice(exerciseIdx, 1);

    setPhases(newPhases);
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you were to test the functionality of this, you'd find that when inputting some value (sets/reps) and trying to drag the element it will set that value to 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K9OhlmXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/324crg276qoy4psko3et.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K9OhlmXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/324crg276qoy4psko3et.gif" alt="Image description" width="880" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's because of that blur listener. To avoid this, I created a ref&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const inFocus = React.useRef&amp;lt;HTMLInputElement&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which I'm setting on input focus like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input onFocus={(e) =&amp;gt; (inFocus.current = e.target)} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;, then, I just add prop to &lt;code&gt;DragDropContext&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DragDropContext
  onBeforeDragStart={() =&amp;gt; inFocus.current?.blur()}
  ... 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the end, we can look up exercises and drag and drop them into particular phases. But, that's not the end cause we have to submit them to the backend. Will do that in the next part. See you there 😉&lt;/p&gt;

</description>
      <category>react</category>
      <category>devjournal</category>
      <category>todayilearned</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building Training Plan builder: Introduction</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Sun, 31 Jul 2022 10:41:00 +0000</pubDate>
      <link>https://forem.com/_andi_/building-training-plan-builder-introduction-c93</link>
      <guid>https://forem.com/_andi_/building-training-plan-builder-introduction-c93</guid>
      <description>&lt;p&gt;Hello! So, I started a new mini side project. The app is supposed to help you create a training plan and export it in PDF format. In this article, I'm going to talk about setting up the project and building authentication and DB schema. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting the project up
&lt;/h2&gt;

&lt;p&gt;I did the setup with the help of this &lt;a href="https://www.prisma.io/blog/fullstack-remix-prisma-mongodb-1-7D0BfTXBmB6r"&gt;tutorial&lt;/a&gt;. Sabin Adams will help you set up the development environment with Remix, Tailwind, MongoDB, and Prisma. I don't want to take credit for that, so if you want to set up a simple project, take a look at that article/tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;This part will be pretty short. Mainly because, I did a lot of the same stuff as I did in the previous project and again there's already a great article talking about the auth in Remix with MongoDB, that I followed. You can find it &lt;a href="https://dev.to/ishanme/fullstack-authentication-with-remix-using-prisma-mongodb-and-typescript-c1e"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  User info in the app
&lt;/h3&gt;

&lt;p&gt;When it came to keeping user information on the frontend I immediately went to creating context. But I felt a little lazy and I didn't need user data in a lot of places, so because I have the info saved in the request header I decided to create a simple function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const authenticateUser = async (req: Request) =&amp;gt; {
  const user = await getUser(req);

  if (!user) throw redirect("auth/login");

  return user;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;, which I call in loader and get the result in the component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const loader: LoaderFunction = async ({ request }) =&amp;gt; {
  const user = await authenticateUser(request);

  return user;
};
...
// in Route component
const user = useLoaderData&amp;lt;Awaited&amp;lt;ReturnType&amp;lt;typeof authenticateUser&amp;gt;&amp;gt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is then passed to &lt;code&gt;Layout&lt;/code&gt; component, which displays the name of the user in the navbar&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Navbar = (props: { user: { name: string; id: string } }) =&amp;gt; {
  const { user } = props;

  return (
    &amp;lt;div className="navbar bg-base-100"&amp;gt;
      &amp;lt;Link to="/" className="btn btn-ghost normal-case text-xl"&amp;gt;
        Strongion
      &amp;lt;/Link&amp;gt;
      &amp;lt;div className="ml-auto font-semibold text-lg"&amp;gt;{user.name}&amp;lt;/div&amp;gt;
      &amp;lt;form action="/auth/logout" method="POST"&amp;gt;
        &amp;lt;button name="_action" value="delete" className="btn btn-nav-sm ml-2"&amp;gt;
          Log out
        &amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const Layout: React.FC&amp;lt;{ user: { name: string; id: string } }&amp;gt; = (props) =&amp;gt; {
  const { user, children } = props;

  return (
    &amp;lt;div className="h-screen bg-base-100"&amp;gt;
      &amp;lt;Navbar user={user} /&amp;gt;
      &amp;lt;div className="w-2/3 mx-auto"&amp;gt;{children}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  DB Schema
&lt;/h2&gt;

&lt;p&gt;I created the schema with four main models: User, Plan, Phase, Exercise. Each user can create multiple Plans, so we get one-to-many relations. Each plan has multiple Phases, and each Phase has multiple exercises attached to it. All relations are of type one-to-many. Here's the whole schema with attributes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
  id            String   @id @default(auto()) @map("_id") @db.ObjectId
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  name          String   @unique
  password      String
  trainingPlans Plan[]
}

model Plan {
  id          String   @id @default(auto()) @map("_id") @db.ObjectId
  title       String   @unique
  description String?
  trainee     User     @relation(references: [id], fields: [traineeId], onDelete: Cascade)
  traineeId   String   @db.ObjectId
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  phases      Phase[]
}

model Phase {
  id          String     @id @default(auto()) @map("_id") @db.ObjectId
  title       String
  description String?
  exercises   Exercise[]
  plan        Plan       @relation(references: [id], fields: [planId], onDelete: Cascade)
  planId      String     @db.ObjectId

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Exercise {
  id           String       @id @default(auto()) @map("_id") @db.ObjectId
  name         String
  description  String?
  exerciseData ExerciseData
  phase        Phase        @relation(references: [id], fields: [phaseId], onDelete: Cascade)
  phaseId      String       @db.ObjectId

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

type ExerciseData {
  reps Int
  sets Int
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this section, we went through setting up a simple project, authentication, and creating a DB schema. If you ask me, pretty uninteresting things, but they have to be done 😅, so, to make the project more exciting, in the next section I'm going to explain building the Drag and Drop component with autocomplete search. See you there 😉 &lt;/p&gt;

&lt;p&gt;Github -&amp;gt; &lt;a href="https://github.com/Attanox/strongion"&gt;Strongion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>devjournal</category>
      <category>todayilearned</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Create fullstack book app: CRUD operations w/ tRPC</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Wed, 13 Jul 2022 17:13:01 +0000</pubDate>
      <link>https://forem.com/_andi_/create-fullstack-book-app-crud-operations-w-trpc-406e</link>
      <guid>https://forem.com/_andi_/create-fullstack-book-app-crud-operations-w-trpc-406e</guid>
      <description>&lt;p&gt;Hello! In this part of the series, I'd like to talk a little bit about CRUD operations done via tRPC in my made-up book app. That means our book app should be able to &lt;strong&gt;C&lt;/strong&gt;reate, &lt;strong&gt;U&lt;/strong&gt;pdate, &lt;strong&gt;R&lt;/strong&gt;ead and &lt;strong&gt;D&lt;/strong&gt;elete records. Also, we'll fetch (read) data from external API. At first, all the operations may feel a bit overwhelming. That's why I prefer to list all of the operations and start crossing them out of the list, one by one.&lt;/p&gt;

&lt;p&gt;Here's my list&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a chapter (C)&lt;/li&gt;
&lt;li&gt;display book detail with all the chapters (R)&lt;/li&gt;
&lt;li&gt;update a chapter (U)&lt;/li&gt;
&lt;li&gt;delete a chapter (D)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think it makes sense to start with the read portion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Display book detail with all the chapters
&lt;/h2&gt;

&lt;p&gt;To get a book detail, first, we need to have its ID, in this case, we'll be using the isbn13 code. I've previously mentioned an API I've chosen to work with called &lt;a href="https://api.itbook.store/"&gt;https://api.itbook.store/&lt;/a&gt;. The API can fetch the newest books by calling &lt;a href="https://api.itbook.store/1.0/new"&gt;https://api.itbook.store/1.0/new&lt;/a&gt;. You can look at what's being returned. I'll make a type of just some attributes. On top of that, I could've mapped over the result, but this isn't a production site and so I didn't bother. I got my fetch function from &lt;a href="https://kentcdodds.com/blog/using-fetch-with-type-script"&gt;Kent C. Dodds' blog&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export type Book = {
  title: string;
  subtitle: string | null;
  isbn13: string;
  price: string;
  image: string;
};

export type BookList = {
  error: number;
  total: number;
  page?: number;
  books: Book[];
};

export function fetchBooks(type = "", customConfig = {}): Promise&amp;lt;BookList&amp;gt; {
  const config = {
    method: "GET",
    ...customConfig,
  };

  const url = `${process.env.BOOKS_URL}${type}`;

  return fetch(url, config).then(async (response) =&amp;gt; {
    if (response.ok) {
      return await response.json();
    } else {
      const errorMessage = await response.text();
      return Promise.reject(new Error(errorMessage));
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I've done at first was to create a new router and chained a &lt;code&gt;query&lt;/code&gt; function that calls &lt;code&gt;fetchBooks&lt;/code&gt; function above, like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const booksRouter = createRouter()
  .query("newest", {
    async resolve() {
      const bookList = await fetchBooks("new");

      return bookList.books;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not sure how I feel about it yet, but I don't think this is the best, so either call it with react query&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const result = useQuery('newest', async () =&amp;gt; await fetchBooks('new')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or in &lt;code&gt;getServerSideProps&lt;/code&gt; of page component and pass it as props&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Props = {
  books: Books;
};

export const getServerSideProps: GetServerSideProps&amp;lt;Props&amp;gt; = async (
  ctx
) =&amp;gt; {
  const book = await fetchBook('new');

  return {
    props: {
      books,
    },
  };
};

const NewestBooks = ({
  books,
}: InferGetServerSidePropsType&amp;lt;typeof getServerSideProps&amp;gt;) =&amp;gt; {
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a list of the newest books we can loop over them. Let's display some cards or something, important thing is we have to have a link to an individual book detail page. In general, you'll have something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;books.maps((book) =&amp;gt; {
  return (
    &amp;lt;div className="card"&amp;gt;
       ...
       &amp;lt;Link href={`/books/${book.isbn13}`}&amp;gt;
         &amp;lt;a&amp;gt;...&amp;lt;/a&amp;gt;
       &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  )
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Style it however you want. Don't forget to create new page at &lt;code&gt;pages/books/[isbn13]/index.tsx&lt;/code&gt;. This might look weird, but later on, when working with chapters, I want to have URLs like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/books/[isbn13]/chapter/add &amp;lt;- for adding new chapter
and
/books/[isbn13]/chapter/[id] &amp;lt;- for updating existing chapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It probably doesn't make much of a difference, so don't sweat it if you'll have different file structure/routes, as long as it makes sense to you 😄. We're already at creating pages, so let's create pages for adding and updating chapters, like I mentioned previously.&lt;/p&gt;

&lt;p&gt;At this point, we have the "newest books" page, "book detail" page, "add chapter" page and "edit chapter" page. In "book detail" page, we'll fetch the book by ISBN code by calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useRouter } from 'next/router'

const BookDetail = () =&amp;gt; {
  const router = useRouter()
  const { isbn13 } = router.query
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or in getServerSideProps&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getServerSideProps: GetServerSideProps&amp;lt;LocalProps&amp;gt; = async (
  ctx
) =&amp;gt; {
  const code = ctx?.params?.isbn13;
  ...
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can fetch a book by isbn13 code by calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function fetchBook(isbn13 = "", customConfig = {}): Promise&amp;lt;Book&amp;gt; {
  const config = {
    method: "GET",
    ...customConfig,
  };

  const url = `${process.env.BOOKS_URL}books/${isbn13}`;

  return fetch(url, config).then(async (response) =&amp;gt; {
    if (response.ok) {
      return await response.json();
    } else {
      const errorMessage = await response.text();
      return Promise.reject(new Error(errorMessage));
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, like we did fetch the newest books.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getServerSideProps: GetServerSideProps&amp;lt;LocalProps&amp;gt; = async (
  ctx
) =&amp;gt; {
  const book = ctx.params
    ? await fetchBook(ctx?.params?.isbn13 as string)
    : null;

  return {
    props: {
      book,
    },
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;sidenote: if you used &lt;code&gt;useRouter&lt;/code&gt;, then fetch with react-query&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks to this request, the book detail may contain additional book info, but I definitely want list of chapters, that I can edit and delete, and I want to be able to create a new chapter. &lt;/p&gt;

&lt;h2&gt;
  
  
  Create a chapter
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;sidenote: when talking about the next chapter we're gonna be creating, lets use term, "callable" pieces of code. When talking about a query, picture using it at the top of some component, on the other hand, when talking about a mutation, picture some kind of a button user will be clicking&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Creating a chapter makes sense to do the next, because I want to have some chapters in DB first, which I'll edit or delete later. The scenario is: user comes to the book detail page, where they can either add the book to their "library", or if they've already done so, can add a new chapter.&lt;br&gt;
Let's define, that both of these actions are done by clicking some kind of a button. At this stage, I'm not thinking about which one I'm gonna show or not. I'll be doing testing and I know in which order I have to be clicking buttons. &lt;br&gt;
Creating mutations for both buttons is a safe bet. I'll show you adding the book first&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .mutation("add-book", {
    input: z.object({
      title: z.string(),
      subtitle: z.string().nullish(),
      isbn13: z.string(),
      price: z.string(),
      image: z.string(),
    }),
    async resolve({ ctx, input }) {
      const bookNote = await ctx.prisma.bookNote.create({
        data: {
          ...input,
          authorId: ctx.user?.id || "",
        },
      });

      return bookNote;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you remember &lt;code&gt;context&lt;/code&gt; from the previous post? Having a user at each function is super handy, like here. So, we'll create a new book note. Once that's done, we can add a chapter by calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .mutation("add-chapter", {
    input: z.object({
      bookID: z.string(),
      payload: z.object({
        title: z.string(),
        text: z.string(),
      }),
    }),
    async resolve({ ctx, input }) {
      const chapter = await ctx.prisma.chapter.create({
        data: {
          ...input.payload,
          bookNoteId: input.bookID,
        },
      });

      return chapter;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both cases, we can generalize, that when creating a new record, we call &lt;code&gt;create&lt;/code&gt; method of the particular model. There, we're passing a &lt;code&gt;data&lt;/code&gt; object, that will expect certain values defined in the schema.&lt;/p&gt;

&lt;p&gt;You hook to this mutation like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { trpc } from "utils/trpc"

const exampleMutation = trpc.useMutation(["router.example"]);

...

&amp;lt;button disabled={exampleMutation.isLoading} onClick={exampleMutation.mutate} &amp;gt;Click to mutate&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usually, after mutating, one wants to let's say refetch some kind of a query or redirect the user. Just as with react-query, you can use &lt;code&gt;onSuccess&lt;/code&gt; attribute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const exampleMutation = trpc.useMutation(["router.example"], {
  onSuccess: (data) =&amp;gt; {...}
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we have two buttons, but in production, I'd like to switch between them. I'll have to be checking if I already have the book in my "library", and that way I'll decide which button to show&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .query("get-book", {
    input: z.object({
      isbn13: z.string(),
    }),
    async resolve({ ctx, input }) {
      const bookNote = await ctx.prisma.bookNote.findFirst({
        where: {
          authorId: String(ctx.user?.id),
          isbn13: input.isbn13,
        },
      });

      return bookNote;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we're asking Prisma to give us a record with the given isbn13 and author. We call this query like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { trpc } from 'utils/trpc'

const exampleQuery = trpc.useQuery(["router.example", $input$]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could have a different flow of adding chapters. Like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .mutation("add-chapter", {
    input: z.object({
      book: z.object({
        title: z.string(),
        subtitle: z.string().nullish(),
        isbn13: z.string(),
        price: z.string(),
        image: z.string(),
      }),
      payload: z.object({
        title: z.string(),
        text: z.string(),
      }),
    }),
    async resolve({ ctx, input }) {
      const bookNote = await ctx.prisma.bookNote.upsert({
        where: {
          id: input.book.id 
        },
        update: {},
        data: {
          ...input.book,
          authorId: ctx.user?.id || "",
        },
      });

      const chapter = await ctx.prisma.chapter.create({
        data: {
          ...input.payload,
          bookNoteId: input.book.id,
        },
      });

      return chapter;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This option uses &lt;code&gt;upsert&lt;/code&gt;, which can be used as find or create. We're saying to find a book or create it otherwise, which ensures that, when we're adding a chapter, there will be a record with &lt;strong&gt;id&lt;/strong&gt; of &lt;code&gt;input.book.id&lt;/code&gt;. Your choice, I'll go with the first one 😀.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update a chapter
&lt;/h2&gt;

&lt;p&gt;Definition of function for updating a chapter is pretty straightforward, when adding a chapter, we were creating a record, which is C out of CRUD. When updating, you guessed it, we'll use the update&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .mutation("update-chapter", {
    input: z.object({
      chapterID: z.string(),
      payload: z.object({
        title: z.string(),
        text: z.string(),
      }),
    }),
    async resolve({ ctx, input }) {
      const chapter = await ctx.prisma.chapter.update({
        data: {
          ...input.payload,
        },
        where: {
          id: input.chapterID,
        },
      });

      return chapter;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Delete a chapter
&lt;/h2&gt;

&lt;p&gt;And at the end, we're deleting (literally calling delete).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .mutation("remove-chapter", {
    input: z.object({
      chapterID: z.string(),
    }),
    async resolve({ ctx, input }) {
      await ctx.prisma.chapter.delete({
        where: {
          id: input.chapterID,
        },
      });

      return true;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the mutations and query can be called the way you want, or the way it suits you best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I know, that was intense! But, you've made it! And know you know how to define CRUD functions with tRPC. I hope you can see just how simple it can be.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and keep an eye out for the next part. Till then take care 😉&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>trpc</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Create a fullstack book app: Authentication and DB models</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Mon, 11 Jul 2022 12:51:59 +0000</pubDate>
      <link>https://forem.com/_andi_/authentication-and-db-models-2m6e</link>
      <guid>https://forem.com/_andi_/authentication-and-db-models-2m6e</guid>
      <description>&lt;p&gt;Hello! In this part of the series, we'll dive into authentication and how I designed the database schema. Initially, I wanted to do just authentication, but when adding a user model with Prisma I realized it would be wise to create all models, so I get a bird's eye view of the whole DB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building schema
&lt;/h2&gt;

&lt;p&gt;I'll be using Prisma for working with DB. But Andi, what is Prisma? Glad you asked. It is a database ORM, which is a layer of abstraction, that helps you with DB actions, like searching in DB or creating new records. On top of that, it has full type safety.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create-t3-app&lt;/code&gt; does a lot of initializing for you. You'll have a folder called &lt;code&gt;prisma&lt;/code&gt; at the root of your project, which will have &lt;code&gt;schema.prisma&lt;/code&gt; file containing this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generator client {
    provider = "prisma-client-js"
}

datasource db {
    provider = "sqlite"
    url      = "file:./db.sqlite"
    // url      = env("DATABASE_URL")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This essentially sets your DB of type &lt;code&gt;sqlite&lt;/code&gt;. Now, every time there's change in the schema, to see changes reflected in DB we have to run either&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma db push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was using mainly the first one, because I was quickly prototyping. When running &lt;code&gt;push&lt;/code&gt;, the DB will be wiped. To keep changes you'll want to run the latter.&lt;/p&gt;

&lt;p&gt;So, as I wrote in the last post the DB will consist of users, which will have book-notes assigned to them, and book-notes will have chapters assigned to them. Here are the initial models&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
    id String @id @default(uuid())
}

model BookNote {
    id String @id @default(uuid())
    isbn13 String
}

model Chapter {
    id String @id @default(uuid())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each model has to have an identifier. You can set default value with &lt;code&gt;@default(uuid())&lt;/code&gt;. &lt;del&gt;I didn't do it for the BookNote model, cause a  booknote identifier will be &lt;code&gt;isbn13&lt;/code&gt; code of the book.&lt;/del&gt; That will be received via the external endpoint &lt;a href="https://api.itbook.store/"&gt;https://api.itbook.store/&lt;/a&gt;. &lt;br&gt;
EDIT: yes, the &lt;code&gt;isbn13&lt;/code&gt; code will be received from external endpoint, but we have to set &lt;code&gt;id&lt;/code&gt; to BookNote model, as well. Otherwise, we'll have one bookNote for one book.&lt;/p&gt;

&lt;p&gt;Next up, I added more attributes that either made sense or I could retrieve from the endpoint mentioned above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
    id String @id @default(uuid())

    name String @unique
    password String
}

model BookNote {
    isbn13 String @id 

    title String
    subtitle String?
    image String
    price String

    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt
}

model Chapter {
    id String @id @default(uuid())

    title String
    text String

    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are some special attributes like &lt;code&gt;@unique&lt;/code&gt; which make the column unique (so there are no two names identical). Then, there is &lt;code&gt;@default(now())&lt;/code&gt;, which adds time to &lt;code&gt;createdAt&lt;/code&gt; column, and &lt;code&gt;@updatedAt&lt;/code&gt; which updates time, whenever we make a change.&lt;/p&gt;

&lt;p&gt;Now, as I wrote earlier, each user will have multiple book-notes. Each book-note will have multiple chapters. This leads us to using one-to-many relation. If a book-note would be shareable, i.e. a book-note can have multiple users, I would use many-to-many relation. &lt;/p&gt;

&lt;p&gt;Looking at the docs of &lt;a href="https://www.prisma.io/docs/concepts/components/prisma-schema/relations/one-to-many-relations"&gt;one-to-many&lt;/a&gt; relation (sidenote: don't be scared to look at docs, memorizing is a waste of everybody's time), we get final schema&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
    id String @id @default(uuid())
    name String @unique
    password String
    bookNotes BookNote[]
}

model BookNote {
    isbn13 String @id 
    title String
    subtitle String?
    image String
    price String
    author User @relation(fields: [authorId], references: [id])
    authorId String
    chapters Chapter[]
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt
}

model Chapter {
    id String @id @default(uuid())

    title String
    text String
    bookNote BookNote @relation(fields: [bookNoteId], references: [isbn13])
    bookNoteId String
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JWT Authentication with trpc
&lt;/h2&gt;

&lt;p&gt;First things first... what is JWT? It's a shortcut for &lt;code&gt;jsonwebtoken&lt;/code&gt;. It helps you take some data, like name, email, and essentially hash it and save it to help with identifying user, by unhashing it and checking the data. This token can be saved on the client, which, from what I read, can be dangerous, or can be saved on the server (we'll do that).&lt;/p&gt;

&lt;p&gt;Let's install some packages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i cookie jose bcrypt --save
npm i @types/cookie @types/jose @types/bcrypt --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like to make checklist of everything that has to be done: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on the sign-in, set the user's JWT token in the request's cookie&lt;/li&gt;
&lt;li&gt;verify the given JWT token&lt;/li&gt;
&lt;li&gt;get hashed values out of the JWT token&lt;/li&gt;
&lt;li&gt;on the sign-out, expire user's JWT token in requests cookie&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like to first have an interface, so I can easily test stuff. Next.js has file-based routing, which means, when you create a file like &lt;code&gt;example.tsx&lt;/code&gt; in the pages directory, you already have a route &lt;code&gt;/example&lt;/code&gt;. Let's create &lt;code&gt;sign-up.tsx&lt;/code&gt; and &lt;code&gt;login.tsx&lt;/code&gt; files. Both will follow structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function FormPage() {
  create a function that will send auth data to server and redirect to particular page

  return (
    // render a form that calls function mentioned above
  );
}

export default FormPage;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't want to go to specifics, the main idea is written above. We can refactor to infinity, but that's not important... right now, I just want an interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Register
&lt;/h3&gt;

&lt;p&gt;The whole Backend will be written thanks to &lt;code&gt;tRPC&lt;/code&gt;. It is a technology, that lets you define functions on the Backend and call them on the Frontend. For creating such a function one needs a router. Let's create one in &lt;code&gt;server/router&lt;/code&gt; directory, it will be called &lt;code&gt;auth.router.ts&lt;/code&gt;. Here, we'll create a new router by calling &lt;code&gt;createRouter()&lt;/code&gt; and chaining &lt;code&gt;mutation()&lt;/code&gt; to it. The &lt;code&gt;tRPC&lt;/code&gt; has 2 main types of functions &lt;code&gt;mutation&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt;. I use &lt;code&gt;mutation&lt;/code&gt;, whenever I have to fetch the data on some event, like on click. And I use &lt;code&gt;query&lt;/code&gt;, whenever I just have to fetch stuff in the component. We'll use mutation for sign-up, because we're sending data on click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const authRouter = createRouter()
  .mutation("signup", {
    input: z.object({
      name: z.string(),
      password: z.string(),
    }),
    async resolve({ input, ctx }) {
      const { prisma } = ctx;

      const { name, password } = input;


      const user = await prisma.user.create({
        data: {
          name: name,
          password: bcrypt.hashSync(password, 10),
        },
      });

      return { name: user.name };
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function expects you to send some input, in above-defined format, then creates a new user with &lt;code&gt;Prisma&lt;/code&gt;. On Frontend we'll create mutation via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mutation = trpc.useMutation(["auth.sign-up"]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tRPC is a layer above React-Query, so it works like &lt;code&gt;useMutation&lt;/code&gt; there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Login
&lt;/h3&gt;

&lt;p&gt;Let's chain another function &lt;code&gt;login&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.mutation("login", {
    input: z.object({ name: z.string(), password: z.string() }),
    async resolve({ ctx, input }) {
      const { prisma } = ctx;

      const { name, password } = input;

      const user = await prisma.user.findUnique({
        where: {
          name: name,
        },
      });

      if (bcrypt.compareSync(password, user.password)) {
        const token = await setUserCookie(user.name, ctx.res);
        return { name: user.name, token };
      } 
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login takes some input and searches the DB to find the unique name (that's why @unique is in the schema). Finding users is not enough though, we have to compare passwords. If they're the same we take the name and hash it with JWT and set in on the server. Create a new file &lt;code&gt;auth.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/lib/auth.s

const SECRET = process.env.JWT_SECRET;

export async function setUserCookie(name: string, res: NextApiResponse) {
  try {
    const token = await new SignJWT({})
      .setProtectedHeader({ alg: "HS256" })
      .setJti(name)
      .setIssuedAt()
      .setExpirationTime("2h")
      .sign(new TextEncoder().encode(SECRET));

    res.setHeader(
      "Set-cookie",
      cookie.serialize("token", token, {
        httpOnly: true,
        path: "/",
        maxAge: 60 * 60 * 2, // 2 hours in seconds,
      })
    );

    return token;
  } catch (e) {
    console.error({ setCookies: e });
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signing part is a little scary with all the chaining, but I literally just copied it from their &lt;a href="https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#readme"&gt;docs&lt;/a&gt;. In function, we're setting the header with &lt;code&gt;setHeader&lt;/code&gt; (method of Next API Response). Thanks to package &lt;code&gt;cookie&lt;/code&gt; we can serialize the hashed token to header cookies, here we're setting &lt;code&gt;httpOnly: true&lt;/code&gt;, which makes it so we have cookies on the server and not the client, &lt;code&gt;path&lt;/code&gt; these cookies exists on, and maximum age of the cookie.&lt;/p&gt;

&lt;p&gt;You can see there's also a &lt;code&gt;SECRET&lt;/code&gt; variable. It is a key that hashes the data. We'll be using this value at verifying the given token, speaking of... let's do that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/lib/auth.s

interface UserJwtPayload {
  jti: string;
  iat: number;
}

export async function verifyJWT(token: string) {
  const authSession = await jwtVerify(token, new TextEncoder().encode(SECRET));
  return authSession.payload as UserJwtPayload;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll get some payload from the hashed token. The payload will be of type UserJwtPayload, and the data, we hashed, will be in &lt;code&gt;jti&lt;/code&gt; attribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logout
&lt;/h3&gt;

&lt;p&gt;At last, we want to expire token, when user logs out. For that, let's chain new method in &lt;code&gt;auth.router.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; .mutation("logout", {
    async resolve({ ctx }) {
      await expireUserCookie(ctx.res);

      return true;
    },
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/lib/auth.s

export function expireUserCookie(res: NextApiResponse) {
  res.setHeader(
    "set-cookie",
    cookie.serialize("token", "invalid", {
      httpOnly: true,
      path: "/",
      maxAge: 0,
    })
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  tRPC Context
&lt;/h3&gt;

&lt;p&gt;You may wonder, what is the &lt;code&gt;ctx&lt;/code&gt; variable in &lt;code&gt;resolve&lt;/code&gt;?  The &lt;code&gt;tRPC&lt;/code&gt; comes with the handy thing called &lt;code&gt;context&lt;/code&gt;, which runs on every request (that's the &lt;code&gt;ctx&lt;/code&gt; in router resolve). We can pass the user there, so, we have it at our disposal. You can do that for any other data you feel have to be globally available for each request. If we remove user data from the server cookie, the context will pass &lt;code&gt;null&lt;/code&gt; as user data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const getUserFromCookies = async (req: NextApiRequest) =&amp;gt; {
  // get JWT `token` on cookies
  const token = req.cookies["token"] || "";

  try {
    // if token is invalid, `verify` will throw an error
    const payload = await verifyJWT(token).catch((err) =&amp;gt; {
      console.error(err.message);
    });

    if (!payload) return null;

    // find user in database
    const user = await prisma.user.findUnique({
      where: {
        name: payload.jti,
      },
    });

    return user;
  } catch (e) {
    return null;
  }
};

export const createContext = async ({
  req,
  res,
}: trpcNext.CreateNextContextOptions) =&amp;gt; {
  const user = await getUserFromCookies(req);

  return {
    req,
    res,
    prisma,
    user,
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logout can be tested with a simple button. However, it would be nice to know if the user is logged in on the client-side. Having something like context from tRPC on the client, you know... Oh wait, we have just the thing, &lt;code&gt;React Context&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication Context
&lt;/h2&gt;

&lt;p&gt;Context can be a tricky thing with (what sometimes feels like) random re-renders and such. Thanks to this &lt;a href="https://www.developerway.com/posts/how-to-write-performant-react-apps-with-context"&gt;article&lt;/a&gt; on developerway, created by Nadia Makarevich, I view context differently and use it with more confidence. I really recommend reading it, but long story short, people put a lot of data into the context's state. What you want to do, is split the state to API part, where you call dispatch type of functions and part with some values, like user name, email, and such. Then you create the context for each of these values, so when one changes, this change does not trigger unnecessary rerenders. It will look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const AuthAPIContext = createContext({
  logoutUser: () =&amp;gt; {},
  loginUser: () =&amp;gt; {},
});

const AuthUserContext = createContext({
  user: initialState.user,
});

const reducer = (state: TState, action: TAction): TState =&amp;gt; {
  switch (action.type) {
    case "AUTH/LOGIN":
      return {
        ...state,
        user: action.payload?.name,
      };
    case "AUTH/LOGOUT":
      return {
        ...state,
        user: null,
      };
    default:
      return state;
  }
};

export const AuthProvider = ({ children }) =&amp;gt; {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  const api = React.useMemo(
    () =&amp;gt; ({
      loginUser: (s: string) =&amp;gt; dispatch(loginUser(s)),
      logoutUser: () =&amp;gt; dispatch(logoutUser()),
    }),
    []
  );

  return (
    &amp;lt;AuthUserContext.Provider value={{ user: state.user }}&amp;gt;
      &amp;lt;AuthAPIContext.Provider value={api}&amp;gt;{children}&amp;lt;/AuthAPIContext.Provider&amp;gt;
    &amp;lt;/AuthUserContext.Provider&amp;gt;
  );
};

export const useAuthAPI = () =&amp;gt; useContext(AuthAPIContext);
export const useAuthUser = () =&amp;gt; useContext(AuthUserContext);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Protecting routes
&lt;/h2&gt;

&lt;p&gt;Cool! We have the login system, we have an auth context, but we're still missing some protection for routes. For example, we don't want an unauthenticated user to be looking at some parts of the website. We can do this multiple ways, however, I found two that I personally like the best, HOC and middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Higher Order Component solution
&lt;/h3&gt;

&lt;p&gt;With this solution, we'll create HOC wrapping &lt;code&gt;getServerSideProps&lt;/code&gt;. When there's a user logged in, we can redirect the user by returning&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redirect: {
  permanent: false,
  destination: "/login",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the definition&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const withAuth =
  (getServerSidePropsFn) =&amp;gt;
  async (ctx) =&amp;gt; {
    const token = ctx.req.cookies?.token;

    if (!token) {
      return {
        redirect: {
          permanent: false,
          destination: "/login",
        },
      };
    }

    // if token is invalid, `verify` will throw an error
    const payload = await verifyJWT(token).catch((err) =&amp;gt; {
      console.error(err.message);
    });

    if (!payload) {
      return {
        redirect: {
          permanent: false,
          destination: "/login",
        },
      };
    }

    return getServerSidePropsFn(ctx);
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's how I used it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getServerSideProps = withAuth(async (
  ctx
) =&amp;gt; {
  return {
    props: {
      ...
    },
  };
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this solution, because redirect happens on the server, instead of the client, therefore no flashes, but we have to do this for each and every protected page. Luckily with the newest version of Next.js we might have a solution in form of middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Middleware solution
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;Next 12.2&lt;/code&gt; they released middleware. From Next.js &lt;a href="https://nextjs.org/docs/advanced-features/middleware"&gt;docs&lt;/a&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Middleware allows you to run code before a request is completed, then based on the incoming request, you can modify the response by rewriting, redirecting, adding headers, or setting cookies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is the exact thing we want, we run this middleware, &lt;strong&gt;before&lt;/strong&gt; a request is completed where, we'll check request cookies and if there's something wrong, we redirect the user. It can even run on just particular pages with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const config = {
  matcher: here you write a path or array of paths you want to match,
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how I used it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const config = {
  matcher: ["/my-notes", "/books/:path*"],
};

export async function middleware(req: NextRequest) {
  const verifiedToken = await verifyAuth(req).catch((err) =&amp;gt; {
    console.error(err.message);
  });

  // redirect if the token is invalid
  if (!verifiedToken) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  return NextResponse.next();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Well, that was a lot. But to conclude, we created the whole schema for our DB. Then, we created a whole login system with &lt;code&gt;tRPC&lt;/code&gt; router, JWT tokens, client and server context and we finished up with protecting our routes. &lt;/p&gt;

&lt;p&gt;You can look at the project here &lt;a href="https://github.com/Attanox/it-notes"&gt;https://github.com/Attanox/it-notes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks a lot for reading! The next part will be about CRUD operations. See you there! 😉&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>trpc</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Create a fullstack book app: Introduction</title>
      <dc:creator>Andrej Tlčina</dc:creator>
      <pubDate>Mon, 11 Jul 2022 12:48:49 +0000</pubDate>
      <link>https://forem.com/_andi_/introduction-1hc8</link>
      <guid>https://forem.com/_andi_/introduction-1hc8</guid>
      <description>&lt;p&gt;Hello! In this series of posts, I'd like to document my journey of creating a fullstack app as a frontend dev. Don't get me wrong, I love frontend, but there is something cool about fullstack. Therefore, I decided to create this series to share how I go about creating and learning new stuff, and maybe hopefully you, as a reader, will get something from it. If not or you feel like there is something I explained wrong or could be done better, feel free to hop in the comments 😄&lt;/p&gt;

&lt;h2&gt;
  
  
  Main goal
&lt;/h2&gt;

&lt;p&gt;Have you ever read a book, but then forgotten about the concepts it had, or you've been told to make notes, so it will be easier to comprehend the material? Well, I've been there (no this is not a commercial), but I never stuck with the classic paper notebook. So, I had this idea that instead of creating another To-Do app, I'd make an app that will fetch books from an external API and each book can be given notes. The app would also have authentication, so each user has their own set of notes to a particular book. This way, I'll work with multiple different fullstack concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;The stack I'll use is the one Theo from &lt;a href="https://www.youtube.com/c/TheoBrowne1017"&gt;Theo - ping.gg&lt;/a&gt; YouTube channel recommends. Mainly, because I like the promise of end-to-end type-safety and also &lt;a href="https://www.youtube.com/c/JackHerrington"&gt;Jack Herrington&lt;/a&gt; said it is easy to grasp for beginners. So, in my projects directory I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-t3-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command helped me bootstrap the project. The command also lets you pick technologies. I chose everything (tailwind, prisma, trpc), but the next-auth. I thought by creating my own login system I'd learn a lot, and boy was I right, more on that in the next part of this series, &lt;strong&gt;Authentication&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>trpc</category>
      <category>webdev</category>
      <category>devjournal</category>
    </item>
  </channel>
</rss>
