<?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: Agnar</title>
    <description>The latest articles on Forem by Agnar (@agnarthenomad).</description>
    <link>https://forem.com/agnarthenomad</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%2F1129563%2F7151b3df-3b84-48d1-9f95-b01a9a5ca224.png</url>
      <title>Forem: Agnar</title>
      <link>https://forem.com/agnarthenomad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/agnarthenomad"/>
    <language>en</language>
    <item>
      <title>Dockerise a Waku React project</title>
      <dc:creator>Agnar</dc:creator>
      <pubDate>Wed, 27 Mar 2024 10:20:44 +0000</pubDate>
      <link>https://forem.com/agnarthenomad/dockerise-a-waku-react-project-d76</link>
      <guid>https://forem.com/agnarthenomad/dockerise-a-waku-react-project-d76</guid>
      <description>&lt;p&gt;In this post, I will describe how to containerise a React project that was built with &lt;a href="https://waku.gg/"&gt;Waku&lt;/a&gt; and React Server Components. I will not build the app here but focus on creating the image and container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Make sure you have Docker and Node.js installed on your machine. I was using Node &lt;em&gt;v18.19.0&lt;/em&gt; and Docker &lt;em&gt;v24.0.5&lt;/em&gt;. I built my app with Typescript &lt;em&gt;v5.3&lt;/em&gt;, Tailwind CSS &lt;em&gt;v3.4&lt;/em&gt;, shadcn-ui and &lt;strong&gt;Waku v0.20&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you &lt;strong&gt;really&lt;/strong&gt; want to know
&lt;/h2&gt;

&lt;p&gt;For those that only want the meat and potatoes.&lt;/p&gt;

&lt;p&gt;Let's take a look at &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# import dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package-lock.json .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; public/ public/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ src/&lt;/span&gt;

&lt;span class="c"&gt;# UI library/tools/TS config files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; components.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tailwind.config.js .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; postcss.config.js .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tsconfig.json .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Check if build succeeded&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"/app/dist"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="c"&gt;#start app&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "run", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also used a &lt;code&gt;.dockerignore&lt;/code&gt; file like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/node_modules
.vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I used a &lt;code&gt;docker-compose.yml&lt;/code&gt; file to build the container, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restaurants-fe&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restaurantsapp&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;5000:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, I built the container using the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose up -d --build&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Explanation
&lt;/h2&gt;

&lt;p&gt;If you want a bit more information, here are some details on the setup.&lt;/p&gt;

&lt;p&gt;Let's start with &lt;code&gt;Dockerfile&lt;/code&gt;. I set it up in a way that it uses Docker's &lt;a href="https://docs.docker.com/build/building/multi-stage/"&gt;multi-stage build&lt;/a&gt; paradigm, in which different layers of the process are cached and reused on subsequent builds if the underlying files have not changed since the previous build. Here's what the commands do:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- FROM: use a Node v18 base image to scaffold the build process
- WORKDIR: set a dir path where all the following commands take place
- COPY: copy dependency info in the working dir (signified by the **dot** at the end)
- RUN install: install project dependencies
- COPY public/src: copy other assets (images, jsx files etc.)
- COPY config: copy config files needed for tailwind and shadcn
- RUN build: build the app with production settings
- EXPOSE: make a port available from the container
- CMD: run the start command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you wish, you can include a health check command at the end of the &lt;code&gt;Dockerfile&lt;/code&gt;, which will stop the container if the app is down. Something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:5000 || exit 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now a quick tour of &lt;code&gt;docker-compose.yml&lt;/code&gt;. Of course, this is not necessary for a single container, but I may add more parts to the app in the future and then I can simply add new services to this file. In describing the frontend service: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I specify the build context ( &lt;em&gt;cwd&lt;/em&gt; ) and the dockerfile path. &lt;/li&gt;
&lt;li&gt;I add a container_name, image name, and working dir path for good measure&lt;/li&gt;
&lt;li&gt;I specify any environment variables needed &lt;/li&gt;
&lt;li&gt;I specify port forwarding from the container to the local machine (I used port:5000, but Waku runs on port:8080 by default. You can use that one too, of course)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I build the container and start it up with the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose up -d --build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;where the &lt;code&gt;-d&lt;/code&gt; flag starts the container in the background.&lt;/p&gt;

&lt;p&gt;With that, the container should be ready to be deployed to a cloud environment. I leave the topics of 'Docker hub' and 'Deploying Docker containers' to your Googleing skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word about the Waku app
&lt;/h2&gt;

&lt;p&gt;To start building a waku app, take a look at all the examples and &lt;a href="https://github.com/dai-shi/waku/tree/main/examples"&gt;starters on Waku's Github&lt;/a&gt;. Here I will provide only minimal info for context and potential troubleshooting. The app is a simple website showing info on various restaurants' menus in the area.&lt;/p&gt;

&lt;p&gt;This is my project directory structure.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8umu8e41p0oc0lsafzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8umu8e41p0oc0lsafzu.png" alt="Screenshot of my project's git repo folder structure" width="293" height="680"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
My waku app's file tree (essentials shown only)&lt;br&gt;
&lt;br&gt;

&lt;/p&gt;

&lt;p&gt;All other scaffolding code comes from a starter example or the basic template installed with &lt;code&gt;create waku@latest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the off-chance that you are building with &lt;strong&gt;waku v0.19&lt;/strong&gt;, I have the alternatives as well. Here is the folder structure.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwg0ofnb4ohdkflfjrwzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwg0ofnb4ohdkflfjrwzc.png" alt="Screenshot of my project's git repo folder structure, when built with waku version 0.19" width="297" height="753"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
My waku app's (waku v0.19) file tree (essentials only)&lt;br&gt;
&lt;br&gt;

&lt;/p&gt;

&lt;p&gt;And this is the &lt;code&gt;entries.tsx&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;waku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RootLayout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./templates/root-layout.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./templates/home-page.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AboutPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./templates/about-page.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createLayout&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;createLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dynamic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;createPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AboutPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All other scaffolding code originates just as described above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could not do
&lt;/h2&gt;

&lt;p&gt;In order to decrease the size of the resulting container, I also tried to create an Apache server in the container and copy the static files into its base folder. Similar to what's in &lt;a href="https://www.howtogeek.com/devops/how-to-dockerise-a-react-app/"&gt;this article&lt;/a&gt;. I did the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;.... previous commands ....

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; httpd:alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/local/apache2/htdocs/&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist .&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist/public/index.html .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These instructions copy the static JS files and assets from the &lt;code&gt;/dist&lt;/code&gt; folder of the app into the root folder of the Apache server. It expects that the &lt;code&gt;index.html&lt;/code&gt; file is in this directory, but Waku puts it inside the public directory, so we copy the index file as well. But then, not surprisingly, the relative paths of files referenced in &lt;code&gt;index.html&lt;/code&gt; are wrong :-( .&lt;/p&gt;

&lt;p&gt;I have no experience configuring http servers, so if anyone does and would like to help out, it would be much appreciated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgement
&lt;/h2&gt;

&lt;p&gt;This post was written on the shoulders of other great tutorials on Docker and React apps. I recommend them for further reading. These are sources from &lt;a href="https://www.howtogeek.com/devops/how-to-dockerise-a-react-app/"&gt;howtogeeks&lt;/a&gt;, &lt;a href="https://www.freecodecamp.org/news/how-to-dockerize-a-react-application/"&gt;freecodecamp&lt;/a&gt;, &lt;a href="https://www.knowledgehut.com/blog/web-development/how-to-dockerize-react-app"&gt;Knowledgehut&lt;/a&gt;, Antonio Maccarini on &lt;a href="https://medium.com/@antonio.maccarini/dockerize-a-react-application-with-node-js-postgres-and-nginx-124c204029d4"&gt;Medium&lt;/a&gt; and Dan Murphy on &lt;a href="https://towardsdatascience.com/deploying-a-react-nodejs-application-with-docker-part-i-of-ii-910bc6edb46e"&gt;Towards Data Science&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Here I show how to package a React app built with the new and awesome Waku JS framework into a Docker container for easy deployment on various platforms. As is probably evident from the above, I am not very experienced with Docker and this was an excellent learning opportunity for me. If you have any comments, suggestions or (reasonable) critique :-) I would be very happy to hear from you.&lt;/p&gt;

</description>
      <category>react</category>
      <category>docker</category>
      <category>webdev</category>
      <category>waku</category>
    </item>
    <item>
      <title>How to validate Moment.js Date objects with Zod</title>
      <dc:creator>Agnar</dc:creator>
      <pubDate>Mon, 07 Aug 2023 20:00:23 +0000</pubDate>
      <link>https://forem.com/agnarthenomad/how-to-validate-momentjs-date-objects-with-zod-4lp9</link>
      <guid>https://forem.com/agnarthenomad/how-to-validate-momentjs-date-objects-with-zod-4lp9</guid>
      <description>&lt;p&gt;Have you ever had trouble validating something from a 3rd party library, using Zod? Like a date. Let's take a look.&lt;/p&gt;

&lt;p&gt;Zod is great! Front-end validation using Zod is very helpful and easy as long as you stick to the docs and built-in types. When using non-Zod-native data types in your schema, however, you might get into a bit of a pickle. Just like I did in my first serious Next.js project. Here I want to share how I managed to validate a Moment.js date object from a date picker component inside a submission form. Because I couldn't find a solution online.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6xzhx3c1egr882zwj1qk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6xzhx3c1egr882zwj1qk.png" alt="How to validate a moment.js object in Zod" width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are looking for a quick solution, see above. That's it.&lt;br&gt;
For a little story and explanation, read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little background
&lt;/h2&gt;

&lt;p&gt;Imagine you are building a movie/book/concert/event  listings/calendar type of thing. And you let your users submit new entries in your app. You are building this in React with Typescript. You also do not want to build everything from scratch.&lt;/p&gt;

&lt;p&gt;That is my recent project where users can submit new entries with data, such as &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;start_date&lt;/code&gt;, &lt;code&gt;entry_price&lt;/code&gt; etc. etc. Focusing on the &lt;code&gt;start_date&lt;/code&gt; field, I also chose to use a fancy pre-built date-time picker component. Users can easily pick a date and also a time when the event is happening. This is the &lt;a href="https://www.npmjs.com/package/react-datetime" rel="noopener noreferrer"&gt;react-datetime&lt;/a&gt; component which requires &lt;a href="https://momentjs.com/" rel="noopener noreferrer"&gt;moment.js&lt;/a&gt; to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8t07kehpdttw64k2hj0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8t07kehpdttw64k2hj0n.png" alt="Styled react-datetime picker component" width="466" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if I was working with native JS Date objects, validation would have been straightforward. Zod knows what to do. But the date picker returns its output value as a Moment date object. And this is how I made it work in Zod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breakdown
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkygdvv95d1i9kvw85smk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkygdvv95d1i9kvw85smk.png" alt="Extended example of Moment.js date object validation using Zod" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at a real use-case example and break down the process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;import the main zod object &lt;code&gt;z&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;import the main &lt;code&gt;moment&lt;/code&gt; object and a utility function &lt;code&gt;isMoment&lt;/code&gt; which simply returns a boolean and requires a date as its argument&lt;/li&gt;
&lt;li&gt;import the Typescript declaration of the &lt;code&gt;Moment&lt;/code&gt; date object&lt;/li&gt;
&lt;li&gt;create a Zod schema declaration using the &lt;code&gt;custom&lt;/code&gt; type declaration, provide the validation rule and finally transform the validated data&lt;/li&gt;
&lt;li&gt;Run the validation by passing a Moment.js object into Zod's &lt;code&gt;parse&lt;/code&gt; method&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's get into points #4 and #5 for all the details&lt;/p&gt;

&lt;h3&gt;
  
  
  #4: Building the schema
&lt;/h3&gt;

&lt;p&gt;Not too long ago, Zod added the option to pass in custom types into its schema builder work-flow. We are using this feature and providing it with the &lt;code&gt;Moment&lt;/code&gt; type, that comes in the &lt;code&gt;moment.js&lt;/code&gt; library.&lt;/p&gt;

&lt;p&gt;Next, I am providing a validation check as an arrow function, which simply takes the value that comes in to be checked, and passes it to the &lt;code&gt;isMoment&lt;/code&gt; utility function, also shipped with &lt;code&gt;moment.js&lt;/code&gt;. This returns &lt;code&gt;true/false&lt;/code&gt; based on what is passed in. Below is an example of a Moment.js date object as it comes in from the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8tw8wbfrbdejpy1dyebg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8tw8wbfrbdejpy1dyebg.png" alt="A Moment.js date object as returned from a form" width="776" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the check fails, I am providing an optional error message to be sent back from validation.&lt;/p&gt;

&lt;p&gt;After validation is successful, I am passing the result into Zod's &lt;code&gt;transform&lt;/code&gt; function, where the validated &lt;code&gt;value&lt;/code&gt; is being formatted into an ISO 8601 string, using &lt;code&gt;moment().format()&lt;/code&gt; (check the &lt;a href="https://momentjs.com/docs/#/displaying/format/" rel="noopener noreferrer"&gt;moment docs&lt;/a&gt; for details). Just because my API requires it like that.&lt;/p&gt;

&lt;h3&gt;
  
  
  #5: Parsing the input
&lt;/h3&gt;

&lt;p&gt;Every Zod schema instance has available the &lt;code&gt;parse&lt;/code&gt; method, which is essentially the one doing the validation. When you pass data into it and the data matches its schema, the method returns the value. If it does not match the schema, an error is thrown.&lt;/p&gt;

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

&lt;p&gt;And that's it! I hope this will be helpful and someone can avoid the troubles I went through. Building custom validation schemas with Zod is not too difficult, it only requires some creativity. By using type declarations which are shipped with the 3rd party library and a clever validation check (this could have been written manually as well, but why bother), Zod continues to help with on demand validation and keeps your form in top-shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  Last but not least
&lt;/h3&gt;

&lt;p&gt;Zod is awesome! Simple as that. Automatic and highly complex front-end validation is made easy with this great library. And, of course, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod is Typescript-first&lt;/a&gt;. Make sure to check it out when building your next thing. Shout out to &lt;a href="https://colinhacks.com/" rel="noopener noreferrer"&gt;Colin &lt;/a&gt; for his great work. &lt;em&gt;(This is not sponsored, it's just a great tool).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>zod</category>
      <category>validation</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
