Let's build a contact form with Next.js
Most of the websites have a contact page where you can send a message to reach the owner. They look something like this:
In this article, we will create a similar form with React in Next.js. First, I will create a front-end part with the form, and then I will build an API route which will send the form to your email.
Setting up the application
First, let’s create a new Next.js project. We will create it in a contact-form
folder, with JavaScript and ESLint enabled:
1 2 |
npx create-next-app contact-form <span class="nt">--js</span> <span class="nt">--eslint</span> |
This will create the folder and installs all the dependencies.
Now enter the folder (cd contact-form
) and start the development server:
1 2 |
npm run dev |
Visit http://localhost:3000
to check the running application.
Creating the form
The main file where we are going to make changes is pages/index.js
. Remove the original code inside the file and paste the following code:
1 2 3 |
<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p"><</span><span class="nt">form</span> <span class="na">className</span><span class="p">=</span><span class="s">"container"</span><span class="p">></span> <span class="p"><</span><span class="nt">h1</span><span class="p">></span>Get in touch<span class="p"></</span><span class="nt">h1</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"email block"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-email"</span><span class="p">></span>Email<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-email"</span> <span class="na">type</span><span class="p">=</span><span class="s">"email"</span> <span class="na">name</span><span class="p">=</span><span class="s">"email"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"email"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"block phone"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-phone"</span><span class="p">></span>Phone<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-phone"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"phone"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"tel"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"name block"</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-first"</span><span class="p">></span>First Name<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-first"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"first"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"given-name"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-last"</span><span class="p">></span>Last Name<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-last"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"last"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"family-name"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"message block"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-message"</span><span class="p">></span>Message<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">textarea</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-message"</span> <span class="na">rows</span><span class="p">=</span><span class="s">"6"</span> <span class="na">name</span><span class="p">=</span><span class="s">"message"</span><span class="p">></</span><span class="nt">textarea</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"button block"</span><span class="p">></span> <span class="p"><</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span><span class="p">></span>Submit<span class="p"></</span><span class="nt">button</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"></</span><span class="nt">form</span><span class="p">></span> <span class="p">);</span> <span class="p">}</span> |
This code creates a form with the following fields:
- first name
- last name
- phone number
- message
All fields are required except for the message.
To add style to the form, replace the contents of styles/globals.css
file with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span class="nt">html</span><span class="o">,</span> <span class="nt">body</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="nl">font-family</span><span class="p">:</span> <span class="n">-apple-system</span><span class="p">,</span> <span class="n">BlinkMacSystemFont</span><span class="p">,</span> <span class="n">Segoe</span> <span class="n">UI</span><span class="p">,</span> <span class="n">Roboto</span><span class="p">,</span> <span class="n">Oxygen</span><span class="p">,</span> <span class="n">Ubuntu</span><span class="p">,</span> <span class="n">Cantarell</span><span class="p">,</span> <span class="n">Fira</span> <span class="n">Sans</span><span class="p">,</span> <span class="n">Droid</span> <span class="n">Sans</span><span class="p">,</span> <span class="n">Helvetica</span> <span class="n">Neue</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span> <span class="nl">background</span><span class="p">:</span> <span class="m">#1e1e1e</span><span class="p">;</span> <span class="nl">min-height</span><span class="p">:</span> <span class="m">100vh</span><span class="p">;</span> <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="nl">color</span><span class="p">:</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">243</span><span class="p">,</span> <span class="m">241</span><span class="p">,</span> <span class="m">239</span><span class="p">);</span> <span class="nl">justify-content</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.block</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.name</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">row</span><span class="p">;</span> <span class="nl">justify-content</span><span class="p">:</span> <span class="n">space-between</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.container</span> <span class="p">{</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">1.3rem</span><span class="p">;</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span> <span class="m">85%</span><span class="p">;</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">50px</span><span class="p">;</span> <span class="nl">box-shadow</span><span class="p">:</span> <span class="m">0</span> <span class="m">54px</span> <span class="m">55px</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">78</span> <span class="m">78</span> <span class="m">78</span> <span class="p">/</span> <span class="m">25%</span><span class="p">),</span> <span class="m">0</span> <span class="m">-12px</span> <span class="m">30px</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">78</span> <span class="m">78</span> <span class="m">78</span> <span class="p">/</span> <span class="m">25%</span><span class="p">),</span> <span class="m">0</span> <span class="m">4px</span> <span class="m">6px</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">78</span> <span class="m">78</span> <span class="m">78</span> <span class="p">/</span> <span class="m">25%</span><span class="p">),</span> <span class="m">0</span> <span class="m">12px</span> <span class="m">13px</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">78</span> <span class="m">78</span> <span class="m">78</span> <span class="p">/</span> <span class="m">25%</span><span class="p">),</span> <span class="m">0</span> <span class="m">-3px</span> <span class="m">5px</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">78</span> <span class="m">78</span> <span class="m">78</span> <span class="p">/</span> <span class="m">25%</span><span class="p">);</span> <span class="p">}</span> <span class="nc">.container</span> <span class="nt">input</span> <span class="p">{</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">1.2rem</span><span class="p">;</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">10px</span> <span class="m">0</span> <span class="m">10px</span> <span class="m">0px</span><span class="p">;</span> <span class="nl">border-color</span><span class="p">:</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">31</span><span class="p">,</span> <span class="m">28</span><span class="p">,</span> <span class="m">28</span><span class="p">);</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span> <span class="nl">background-color</span><span class="p">:</span> <span class="m">#e8f0fe</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.container</span> <span class="nt">textarea</span> <span class="p">{</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">10px</span> <span class="m">0</span> <span class="m">10px</span> <span class="m">0px</span><span class="p">;</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span> <span class="nl">border-color</span><span class="p">:</span> <span class="nb">rgb</span><span class="p">(</span><span class="m">31</span><span class="p">,</span> <span class="m">28</span><span class="p">,</span> <span class="m">28</span><span class="p">);</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span> <span class="nl">background-color</span><span class="p">:</span> <span class="m">#e8f0fe</span><span class="p">;</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.container</span> <span class="nt">h1</span> <span class="p">{</span> <span class="nl">text-align</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="nl">font-weight</span><span class="p">:</span> <span class="m">600</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.name</span> <span class="nt">div</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.block</span> <span class="nt">button</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span> <span class="m">30%</span><span class="p">;</span> <span class="nl">border</span><span class="p">:</span> <span class="m">3px</span> <span class="nb">solid</span> <span class="no">black</span><span class="p">;</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.button</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="p">}</span> <span class="nt">textarea</span> <span class="p">{</span> <span class="nl">resize</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span> |
Our form should look something like this:
Now we need to find a way to store the input entered by the user. I will use FormData
which is natively supported in all current browsers. It loads fields from the form, so they can be then submitted to the server.
Inside the pages/index.js
file, paste the following code (notice the new handleSubmit
function):
1 2 3 |
<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">handleSubmit</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="p">(</span> <span class="p"><</span><span class="nt">form</span> <span class="na">className</span><span class="p">=</span><span class="s">"container"</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">handleSubmit</span><span class="si">}</span><span class="p">></span> <span class="p"><</span><span class="nt">h1</span><span class="p">></span>Get in touch<span class="p"></</span><span class="nt">h1</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"email block"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-email"</span><span class="p">></span>Email<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-email"</span> <span class="na">type</span><span class="p">=</span><span class="s">"email"</span> <span class="na">name</span><span class="p">=</span><span class="s">"email"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"email"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"block phone"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-phone"</span><span class="p">></span>Phone<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-phone"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"phone"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"tel"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"name block"</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-first"</span><span class="p">></span>First Name<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-first"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"first"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"given-name"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-last"</span><span class="p">></span>Last Name<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-last"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"last"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"family-name"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"message block"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-message"</span><span class="p">></span>Message<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">textarea</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-message"</span> <span class="na">rows</span><span class="p">=</span><span class="s">"6"</span> <span class="na">name</span><span class="p">=</span><span class="s">"message"</span><span class="p">></</span><span class="nt">textarea</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"button block"</span><span class="p">></span> <span class="p"><</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span><span class="p">></span>Submit<span class="p"></</span><span class="nt">button</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"></</span><span class="nt">form</span><span class="p">></span> <span class="p">);</span> <span class="p">}</span> |
Now when you attempt to submit the form, you should see FormData
in developer console.
Submitting the form to API
We will submit the form data to the API with [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
– no need for additional dependencies.
The form submission logic goes to the handleSubmit
function. Here is the complete code in pages/index.js
:
1 2 3 |
<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Home</span><span class="p">()</span> <span class="p">{</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">handleSubmit</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">);</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/contact</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="na">body</span><span class="p">:</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nx">data</span><span class="p">),</span> <span class="p">});</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`Invalid response: </span><span class="p">${</span><span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span> <span class="p">}</span> <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">Thanks for contacting us, we will get back to you soon!</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">We can't submit the form, try again later?</span><span class="dl">"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="p">(</span> <span class="p"><</span><span class="nt">form</span> <span class="na">className</span><span class="p">=</span><span class="s">"container"</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">handleSubmit</span><span class="si">}</span><span class="p">></span> <span class="p"><</span><span class="nt">h1</span><span class="p">></span>Get in touch<span class="p"></</span><span class="nt">h1</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"email block"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-email"</span><span class="p">></span>Email<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-email"</span> <span class="na">type</span><span class="p">=</span><span class="s">"email"</span> <span class="na">name</span><span class="p">=</span><span class="s">"email"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"email"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"block phone"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-phone"</span><span class="p">></span>Phone<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-phone"</span> <span class="na">type</span><span class="p">=</span><span class="s">"tel"</span> <span class="na">name</span><span class="p">=</span><span class="s">"phone"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"tel"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"name block"</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-first"</span><span class="p">></span>First Name<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-first"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"first"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"given-name"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-last"</span><span class="p">></span>Last Name<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">input</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-last"</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">name</span><span class="p">=</span><span class="s">"last"</span> <span class="na">autoComplete</span><span class="p">=</span><span class="s">"family-name"</span> <span class="na">required</span> <span class="p">/></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"message block"</span><span class="p">></span> <span class="p"><</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"frm-message"</span><span class="p">></span>Message<span class="p"></</span><span class="nt">label</span><span class="p">></span> <span class="p"><</span><span class="nt">textarea</span> <span class="na">id</span><span class="p">=</span><span class="s">"frm-message"</span> <span class="na">rows</span><span class="p">=</span><span class="s">"6"</span> <span class="na">name</span><span class="p">=</span><span class="s">"message"</span><span class="p">></</span><span class="nt">textarea</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"button block"</span><span class="p">></span> <span class="p"><</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span><span class="p">></span>Submit<span class="p"></</span><span class="nt">button</span><span class="p">></span> <span class="p"></</span><span class="nt">div</span><span class="p">></span> <span class="p"></</span><span class="nt">form</span><span class="p">></span> <span class="p">);</span> <span class="p">}</span> |
The handleSubmit
sends data inside a POST
request to the /api/contact
route. We also wrap the FormData
object in URLSearchParams
to send data as application/x-www-form-urlencoded
which is automatically decoded by Next.js API routes handler.
Handling form submission in the API route
Now we need to handle the form submission on the server. We are going to use Next.js API routes for that. API routes are located in pages/api
folder. Let’s create pages/api/contact.js
file which corresponds to the API route /api/contact
.
First inside the pages/api/contact.js
file paste the following code to test if we receive the data on the server.
1 2 3 |
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span> <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="mi">200</span><span class="p">);</span> <span class="p">}</span> |
Try submitting the form now, you should see the data logged on the terminal. And now we are getting to the juicy part.
Sending emails with SendGrid and Superface
When a user submits the contact form, we want to send the submitted information to the website owner. First, we need to pick some email providers and study their API and SDK. Or we can use Superface with any provider.
Superface makes API integrations super easy. We don’t have to deal with API docs, and I can use many providers behind the same interface. Furthermore, I can use more ready-made API use cases from the Superface catalog. It’s a tool worth having in your toolbox.
Set up SendGrid
I’m going to use SendGrid as an email provider with Superface. Create your account, get your API key with Full Access and verify Single Sender Verification.
On Superface side, pick the use case, i.e.: Send Email.
Sending emails from the API route
Superface use cases are consumed with OneSDK, so we will have to install it.
1 2 |
npm i @superfaceai/one-sdk |
On Superface in Send Email use case, select sendgrid
as a provider. We can use most of the code from the example in our API route handler, we just need to pass data correctly from the request.
Paste the following code into your pages/api/contact.js
file:
1 2 3 4 5 |
<span class="kd">const</span> <span class="p">{</span> <span class="nx">SuperfaceClient</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@superfaceai/one-sdk</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">sdk</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SuperfaceClient</span><span class="p">();</span> <span class="c1">// Just check if all required fields are provided</span> <span class="kd">function</span> <span class="nx">formValid</span><span class="p">(</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">body</span><span class="p">.</span><span class="nx">email</span> <span class="o">&&</span> <span class="nx">body</span><span class="p">.</span><span class="nx">phone</span> <span class="o">&&</span> <span class="nx">body</span><span class="p">.</span><span class="nx">first</span> <span class="o">&&</span> <span class="nx">body</span><span class="p">.</span><span class="nx">last</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">default</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">formValid</span><span class="p">(</span><span class="nx">body</span><span class="p">))</span> <span class="p">{</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">422</span><span class="p">).</span><span class="nx">end</span><span class="p">();</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">profile</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">sdk</span><span class="p">.</span><span class="nx">getProfile</span><span class="p">(</span><span class="dl">'</span><span class="s1">communication/[email protected]</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="s2">` Email: </span><span class="p">${</span><span class="nx">body</span><span class="p">.</span><span class="nx">email</span><span class="p">}</span><span class="s2"> Phone: </span><span class="p">${</span><span class="nx">body</span><span class="p">.</span><span class="nx">phone</span><span class="p">}</span><span class="s2"> Name: </span><span class="p">${</span><span class="nx">body</span><span class="p">.</span><span class="nx">first</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">body</span><span class="p">.</span><span class="nx">last</span><span class="p">}</span><span class="s2"> Message: </span><span class="p">${</span><span class="nx">body</span><span class="p">.</span><span class="nx">message</span><span class="p">}</span><span class="s2"> `</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">profile</span><span class="p">.</span><span class="nx">getUseCase</span><span class="p">(</span><span class="dl">'</span><span class="s1">SendEmail</span><span class="dl">'</span><span class="p">).</span><span class="nx">perform</span><span class="p">(</span> <span class="p">{</span> <span class="na">from</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">FROM_EMAIL</span><span class="p">,</span> <span class="na">to</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TO_EMAIL</span><span class="p">,</span> <span class="na">subject</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Message from contact form</span><span class="dl">'</span><span class="p">,</span> <span class="na">text</span><span class="p">:</span> <span class="nx">message</span><span class="p">,</span> <span class="p">},</span> <span class="p">{</span> <span class="na">provider</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sendgrid</span><span class="dl">'</span><span class="p">,</span> <span class="na">security</span><span class="p">:</span> <span class="p">{</span> <span class="na">bearer_token</span><span class="p">:</span> <span class="p">{</span> <span class="na">token</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">SENDGRID_API_KEY</span><span class="p">,</span> <span class="p">},</span> <span class="p">},</span> <span class="p">}</span> <span class="p">);</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">unwrap</span><span class="p">();</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">201</span><span class="p">).</span><span class="nx">end</span><span class="p">();</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">end</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> |
You can notice that we are referring to some environment variables in the code (for example process.env.SENDGRID_TOKEN
). We can store these in .env files. In the root of your project, create a .env.local
file with the following contents (make sure to edit values):
1 2 3 4 5 6 7 |
<span class="c"># Email address verified in Single Sender Verification</span> <span class="nv">FROM_EMAIL</span><span class="o">=</span>from@example.com <span class="c"># Email address where you want to send the submissions to</span> <span class="nv">TO_EMAIL</span><span class="o">=</span>to@example.com <span class="c"># Sendgrid API key</span> <span class="nv">SENDGRID_API_KEY</span><span class="o">=</span>SG.abcdef... |
Our app is ready. Run (or restart) the development server with npm run dev
and try to submit the form!
Conclusion
We have learned how to create a form in Next.js, submit the form with FormData
and fetch, handle the submission in the API route and send it through an email.
Further possible improvements include adding CAPTCHA or honeypot fields to prevent spam submissions, improving form data validation with checks of phone and email address, format the submission as HTML, or providing a nicer feedback to the user upon submissions.
Besides sending an email, we can do a lot more with submissions, like sending data to CRM or Slack, or handle newsletter subscriptions. But that’s for another time – follow our profile or sign up for our monthly newsletter, so you don’t miss our future tutorials.
Source: https://dev.to/superface/lets-build-a-contact-form-with-nextjs-3lao