Forem

Cover image for Simple ChatApp with AWS AppSync Events, CDK & React
Marco Streng
Marco Streng

Posted on

Simple ChatApp with AWS AppSync Events, CDK & React

Image description

A view days ago AWS released the L2 CDK construct for the new AppSync Events API.

With this serverless WebSocket API we can build a simple chat application with just a few lines of code.

Infrastructure

Our architecture diagram is not extensive. We use the mentioned AppSync Events API, an S3 Bucket for storing our React application and CloudFront as CDN.

Image description

CDK

AppSync Events API

We create an AppSync Events API with the new EventsApi construct. For simplicity we keep all the defaults, including API Key as auth provider. In a, lets say, more real-world use case, you probably would choose Cognito which is also supported by AppsSync Events out of the box.

We also create a namespace for our chat application: chat. Channel namespaces provide a scalable way to structure our API. Later in this article we will use this feature to handle multiple chatrooms, like e.g. chat/gardening.

// Events Api
const eventsApi = new appsync.EventApi(this, "EventsApi", {
  apiName: "EventsChat",
});

// Channel
eventsApi.addChannelNamespace("chat");
Enter fullscreen mode Exit fullscreen mode

S3 & CloudFront

For our user interface which will be implemented as a React application we create a S3 Bucket and a CloudFront distribution.

// S3
const clientBucket = new Bucket(this, "ClientBucket", {
  accessControl: BucketAccessControl.PRIVATE,
});
new BucketDeployment(this, "ClientDeployment", {
  destinationBucket: clientBucket,
  sources: [
    Source.asset(path.resolve(__dirname, "[path-to-your-client]")),
  ],
});

// CloudFront
new aws_cloudfront.Distribution(this, "Distribution", {
  defaultRootObject: "index.html",
  errorResponses: [
    {
      httpStatus: 403,
      responseHttpStatus: 200,
      responsePagePath: "/index.html",
    },
    {
      httpStatus: 404,
      responseHttpStatus: 200,
      responsePagePath: "/index.html",
    },
  ],
  defaultBehavior: {
    origin:
      aws_cloudfront_origins.S3BucketOrigin.withOriginAccessControl(
        clientBucket
      ),
  },
});
Enter fullscreen mode Exit fullscreen mode

UI

I recommend setting up a fresh React application with Vite. Additionally I use tailwindcss to implement the UI.

In the following steps I just show the relevant parts. Of course there is some more code for rendering and handling stuff like e.g. focus. See the full example on GitHub.

We use the Amplify library which provides a very easy way to interact with the Events API. The following code is quite close to the official documentation.

Data structure

The data model for the events we will subscribe to consists of id, type and event which stands for the payload.

For this example our payload includes the text of the chat message and information about the author of it.

type Event = {
  id: string;
  type: string;
  event: {
    text: string;
    author: {
      id: string;
      name: string;
    };
  };
};
Enter fullscreen mode Exit fullscreen mode

Subscription

As I already mentioned we can support dynamic chat rooms via channel namespaces. Therefore we simply use the URL pathname. So for the mentioned "gardening" example that could be something like https://www.my-chat-app.com/gardening.

With the following code we can subscribe for messages in our chat room and store those in a React state.

const chatRoom = window.location.pathname;

const [messages, setMessages] = useState<Event[]>([]);

useEffect(() => {
    let channel: EventsChannel;

    const connectAndSubscribe = async () => {
      channel = await events.connect(`chat${chatRoom}`);
      channel.subscribe({
        next: (data: Event) => {
          setMessages((prev) => [...prev, data]);
        },
        error: (err) => console.error("error", err),
      });
    };

    connectAndSubscribe();

    return () => channel && channel.close();
  }, [chatRoom]);
Enter fullscreen mode Exit fullscreen mode

Publishing

Also publishing chat messages is very easy with Amplify.

async function publishMessage(text: string) {
  await events.post(`chat${chatRoom}`, {
    text,
    author: {
      name: user.name,
      id: user.id,
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

Persistence

A note regarding persistence. For now our chat application does not include any persistence layer. That means all chat message just live in the users browser and are gone after closing it. Of course there are multiple ways to add this feature with AWS Services. Since this post is about AppSync Events (in other words: WebSockets 🙂), I won't go into this in depth.

Conclusion

Its great to see what can be achieved just by using those powerful AWS Services, the official code examples and some time playing around with tailwindcss 😁

Full example

You can find the full code example on GitHub.

Suggestions or feedback

If you got any kind of feedback, suggestions or ideas - feel free and write a comment below this article. There is always space for improvement!

Top comments (0)