{"id":31466,"date":"2023-10-13T07:00:00","date_gmt":"2023-10-13T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=31466"},"modified":"2025-11-26T21:27:38","modified_gmt":"2025-11-27T05:27:38","slug":"full-stack-jamstack-application-xata-cloudinary-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js","title":{"rendered":"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js"},"content":{"rendered":"\n<p>There\u2019s been a surge of interest in serverless cloud products due to their scalability, high transparency, faster performance, cost-effectiveness, instant availability, and operating infrastructure support.<\/p>\n\n\n\n<p>Serverless doesn\u2019t mean \u201cno servers.&#8221; It means developers don\u2019t have to manage the servers. Rather, the server is managed by a service provider, and the necessary resources are automatically assigned to developers. As a developer, you can focus more on developing your website or service without worrying about underlying infrastructure and servers.<\/p>\n\n\n\n<p><a href=\"https:\/\/jamstack.org\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">JAMStack<\/a>&nbsp;complements serverless technology, and in this article, you&#8217;ll learn how to build a full-stack JAMStack application using&nbsp;<a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Xata<\/a>,&nbsp;<a href=\"https:\/\/cloudinary.com\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary<\/a>, and&nbsp;<a href=\"https:\/\/nextjs.org\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Next.js<\/a>. Check out the&nbsp;<a href=\"https:\/\/fanciful-puppy-ae9976.netlify.app\/\" target=\"_blank\" rel=\"noreferrer noopener\">live demo<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/github.com\/Giftea\/img-hub-deploy\" target=\"_blank\" rel=\"noreferrer noopener\">source code<\/a>&nbsp;of the Image gallery application you\u2019ll build at the end of this tutorial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#tech-stack\"><\/a>Tech Stack<\/h2>\n\n\n\n<p>Jamstack is unique because it encourages you to combine various technologies to build a full-stack application. The following technologies will be used to build an Image gallery:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Next.js<\/strong>&nbsp;is a React framework for building single-page Javascript applications. With Next.js, you can utilize serverless functions to build your application&#8217;s front end and backend without the need for a backend framework.<\/li>\n\n\n\n<li><strong>Xata<\/strong>&nbsp;is a serverless database with a built-in search and analytics engine. It has an excellent developer experience as it provides an easy-to-navigate dashboard, well-written documentation, a playground, and code snippets.<\/li>\n\n\n\n<li><strong>Cloudinary<\/strong>&nbsp;is an end-to-end solution for digital media. It offers a complete and safe API for quickly uploading media assets.<\/li>\n\n\n\n<li><strong>Netlify<\/strong>&nbsp;is a web hosting platform.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#prerequisites\"><\/a>Prerequisites<\/h2>\n\n\n\n<p>Before getting started with this tutorial, you should have the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Knowledge of React and React hooks.<\/li>\n\n\n\n<li>Basic understanding of ES6 Javascript features.<\/li>\n\n\n\n<li><code>yarn<\/code>&nbsp;or&nbsp;<code>npm<\/code>&nbsp;installed as a package manager.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#getting-started\"><\/a>Getting Started<\/h2>\n\n\n\n<p>Clone the starter project in your preferred directory with the&nbsp;<code>git<\/code>&nbsp;command below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">git <span class=\"hljs-keyword\">clone<\/span> --branch starter https:<span class=\"hljs-comment\">\/\/github.com\/Giftea\/image-hub.git<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Run&nbsp;<code>yarn<\/code>&nbsp;or&nbsp;<code>npm install<\/code>&nbsp;to install all dependencies, and&nbsp;<code>yarn dev<\/code>&nbsp;or&nbsp;<code>npm run dev<\/code>&nbsp;to start the project on&nbsp;<a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#setting-up-xata\"><\/a>Setting Up Xata<\/h2>\n\n\n\n<p>To use <a href=\"https:\/\/app.xata.io\/signin?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Xata<\/a>, you have to create an account and set up your development environment.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--FK7_o7VZ--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667654497858_Screenshot%2B2022-11-05%2Bat%2B14.20.15.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220744\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-1.png\" alt=\"Xata workspace\"\/><\/a><\/figure>\n\n\n\n<p>Automatically, a&nbsp;<strong>workspace<\/strong>&nbsp;will be created for you. A workspace is similar to a GitHub organization; it\u2019s a grouping of databases where you can also invite collaborators and work together on those databases.<\/p>\n\n\n\n<p>Click <strong>Add a database<\/strong> to create a database in your new workspace.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--vQclCbdI--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667655043727_Screenshot%2B2022-11-05%2Bat%2B14.29.45.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220746\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-2.png\" alt=\"Xata Database\"\/><\/a><\/figure>\n\n\n\n<p>With the click of a button, a full-featured database was created. The database schema is made up of tables, which you&#8217;ll use to design the business model of your website.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--2oD593ON--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667655886056_Screenshot%2B2022-11-05%2Bat%2B14.44.30.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220749\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-3.png\" alt=\"Database Schema\"\/><\/a><\/figure>\n\n\n\n<p>Let\u2019s add a table. Click <strong>Add a table<\/strong> and name it &#8220;images&#8221;. Automatically, the new table has a column&nbsp;<strong>id<\/strong>. A unique id is generated for every record on a table.<\/p>\n\n\n\n<p>Each image should have an image URL and tags. Go ahead and add two new columns to the images table:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>img_url<\/code>: String<\/li>\n\n\n\n<li><code>tags<\/code>: Multiple Select<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764221200\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-13.gif\" alt=\"\"\/><\/figure><\/div>\n\n\n<p>The image gallery will have users, and each user can create an image, so let\u2019s create a new \u201cusers\u201d&nbsp;table and link it to our images table. <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>firstname<\/code>: String<\/li>\n\n\n\n<li><code>lastname<\/code>: String<\/li>\n\n\n\n<li><code>password<\/code>: String<\/li>\n\n\n\n<li><code>email<\/code>: Xata provides an \u201cEmail\u201d type for emails. Also, mark email as \u201cUnique\u201d.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--H0kdp9gY--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667762316531_Group%2B37.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220751\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-4.png\" alt=\"user table\"\/><\/a><\/figure>\n\n\n\n<p>Now, let\u2019s link the&nbsp;<strong>users<\/strong>&nbsp;table to the images table. To do this, add a new column to the images table, give it a type of \u201cLink\u201d, name it&nbsp;<code>user<\/code>, and select the&nbsp;<strong>users<\/strong>&nbsp;table to link both tables.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--lfUl0Ikr--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667658271470_Screenshot%2B2022-11-05%2Bat%2B15.24.03.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220754\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-6.png\" alt=\"Link user table\"\/><\/a><\/figure>\n\n\n\n<p>The database schema for the project is all set; database management has never been simpler. The following steps will be taken to set up the Xata development environment on our local machine:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run&nbsp;<code>npm install @xata.io\/cli -g<\/code>&nbsp;to install the CLI globally.<\/li>\n\n\n\n<li>Next run&nbsp;<code>xata auth login<\/code>&nbsp;in your terminal.\n<ul class=\"wp-block-list\">\n<li>Select <strong>Create new API key in browser<\/strong>.<\/li>\n\n\n\n<li>Give your API key a name to create an API key.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Now Xata CLI is installed and you\u2019re authorized. Navigate back to your Xata dashboard.<\/li>\n\n\n\n<li>On the left sidebar, click the&nbsp;<strong>images<\/strong>&nbsp;table.<\/li>\n\n\n\n<li>Click <strong>&lt;\/&gt; Get code snippet<\/strong> at the top-right corner.<\/li>\n\n\n\n<li>You should see some commands to set up dev environment.<\/li>\n\n\n\n<li>We\u2019ve installed the CLI already so copy the second command.<\/li>\n\n\n\n<li>Back in your terminal run the command in your project folder.<\/li>\n\n\n\n<li>Select the following options for the prompts:<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    \u2714 Do you want to use code generation <span class=\"hljs-keyword\">in<\/span> your project? \u203a Generate JavaScript code <span class=\"hljs-keyword\">with<\/span> ES modules\n    \u2714 Choose the output file <span class=\"hljs-keyword\">for<\/span> the code generator \u2026 src\/xata.js\n    \u2714 Do you want to generate the TypeScript declarations? \u2026 no\n    \u2714 Choose a <span class=\"hljs-keyword\">default<\/span> development branch (fallback branch). \u203a &lt;None&gt;\n     \u203a   Warning: Your .env file already contains an API key. The old API key will be ignored after updating the file.\n    \u2714 Your XataClient is generated at .\/src\/xata.js\n    \u2714 Project configured successfully.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the root of your project, you should see an&nbsp;<code>env<\/code>&nbsp;file with your&nbsp;<code>XATA_API_KEY<\/code>&nbsp;and your&nbsp;<code>XataClient<\/code>&nbsp;in&nbsp;<code>\/src\/xata.js<\/code>.<\/p>\n\n\n\n<p>All Xata configurations are done. Let\u2019s start writing some code!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#create-user\"><\/a>Create User<\/h2>\n\n\n\n<p>Add a new file to the&nbsp;<code>\/pages\/api\/<\/code>&nbsp;directory, name it&nbsp;<code>register.js<\/code>&nbsp;and add the code block below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-comment\">\/\/ pages\/api\/register.js<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>; <span class=\"hljs-comment\">\/\/ import XataClient func<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { promisify } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"util\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> bcrypt <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"bcryptjs\"<\/span>; <span class=\"hljs-comment\">\/\/ bcrypt to hash user password<\/span>\n\n    <span class=\"hljs-keyword\">const<\/span> hash = promisify(bcrypt.hash); <span class=\"hljs-comment\">\/\/ hash password<\/span>\n    <span class=\"hljs-keyword\">const<\/span> xata = getXataClient(); <span class=\"hljs-comment\">\/\/ initialize XataClient<\/span>\n\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">register<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> { firstname, lastname, email, password } = req.body; <span class=\"hljs-comment\">\/\/ destructure user input from req.body<\/span>\n      <span class=\"hljs-keyword\">const<\/span> userExist = <span class=\"hljs-keyword\">await<\/span> xata.db.users.filter({ email }).getFirst(); <span class=\"hljs-comment\">\/\/ fetch user from database using email address<\/span>\n\n      <span class=\"hljs-keyword\">if<\/span> (userExist) {\n        res.status(<span class=\"hljs-number\">400<\/span>);\n        <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"User already exists\"<\/span>); <span class=\"hljs-comment\">\/\/ throw error if user with email already exists<\/span>\n      }\n\n      <span class=\"hljs-comment\">\/\/ CREATE A NEW USER IF NO USER WITH THE EMAIL ADDRESS EXISTS<\/span>\n      <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> xata.db.users.create({\n        firstname,\n        lastname,\n        email,\n        <span class=\"hljs-attr\">password<\/span>: <span class=\"hljs-keyword\">await<\/span> hash(password, <span class=\"hljs-number\">10<\/span>),\n      });\n\n      res.json({ <span class=\"hljs-attr\">message<\/span>: <span class=\"hljs-string\">\"Success\"<\/span> });\n      <span class=\"hljs-keyword\">if<\/span> (!user) {\n        res.status(<span class=\"hljs-number\">400<\/span>);\n        <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"Invalid user data\"<\/span>);\n      }\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code block above,&nbsp;<code>getXataClient<\/code>&nbsp;is used to initialize&nbsp;<code>xata<\/code>&nbsp;and&nbsp;<code>bcrypt<\/code>&nbsp;is used to create a&nbsp;<code>hash<\/code>&nbsp;for the user\u2019s password.<\/p>\n\n\n\n<p>In the&nbsp;<code>register<\/code>&nbsp;function, the user\u2019s details are destructured from&nbsp;<code>req.body<\/code>, and the user&nbsp;<code>email<\/code>&nbsp;is used to check if the user already exists. If the user exists, an&nbsp;<code>error<\/code>&nbsp;is returned, otherwise, a new user account is created and added to the database.<\/p>\n\n\n\n<p>Navigate to&nbsp;<code>\/pages\/register.js<\/code>&nbsp;and update the&nbsp;<code>handleOnSubmit<\/code>&nbsp;function as shown below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">  <span class=\"hljs-keyword\">const<\/span> handleOnSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n   e.preventDefault();\n   setLoading(<span class=\"hljs-literal\">true<\/span>)\n   <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/register\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n   <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ firstname, lastname, email, password }),\n     }).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">t<\/span>) =&gt;<\/span> t.json());\n     router.push(<span class=\"hljs-string\">'\/login'<\/span>)\n     setLoading(<span class=\"hljs-literal\">false<\/span>)\n   } <span class=\"hljs-keyword\">catch<\/span> (error) {\n     <span class=\"hljs-built_in\">console<\/span>.log(error);\n   }\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Click <strong>Join<\/strong> at the top-right corner of the web page to navigate to the register page. Fill in the input fields and register a user. If successful, you\u2019d be redirected to the login page.<\/p>\n\n\n\n<p>Go back to your Xata dashboard and check if the&nbsp;<strong>users<\/strong>&nbsp;table has been updated.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--VFysPwh4--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/i.imgur.com\/XtRhFHi.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220757\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-7.png\" alt=\"Xata dashboard\"\/><\/a><\/figure>\n\n\n\n<p>We\u2019ve created our first user record! Let\u2019s authenticate the new user to access other features of the application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#authenticate-user\"><\/a>Authenticate User<\/h2>\n\n\n\n<p>Add a new file to&nbsp;<code>\/pages\/api\/<\/code>&nbsp;directory, name it&nbsp;<code>login.js<\/code>&nbsp;and add the code block below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ \/pages\/api\/login<\/span>\n    <span class=\"hljs-keyword\">import<\/span> cookie <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cookie\"<\/span>; <span class=\"hljs-comment\">\/\/ import cookie<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>; <span class=\"hljs-comment\">\/\/ import XataClient func<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { promisify } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"util\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> bcrypt <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"bcryptjs\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> jwt <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'jsonwebtoken'<\/span>\n\n    <span class=\"hljs-keyword\">const<\/span> compare = promisify(bcrypt.compare); <span class=\"hljs-comment\">\/\/ to compare password<\/span>\n    <span class=\"hljs-keyword\">const<\/span> xata = getXataClient(); <span class=\"hljs-comment\">\/\/ initialize XataClient<\/span>\n    <span class=\"hljs-keyword\">const<\/span> KEY = <span class=\"hljs-string\">\"i_love_xata_&amp;_cloudinary\"<\/span>; <span class=\"hljs-comment\">\/\/ JWT secret<\/span>\n\n    <span class=\"hljs-keyword\">const<\/span> login = <span class=\"hljs-keyword\">async<\/span> (req, res) =&gt; {\n      <span class=\"hljs-keyword\">const<\/span> { email, password } = req.body; <span class=\"hljs-comment\">\/\/ destructure user input from req.body<\/span>\n      <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> xata.db.users.filter({ email }).getFirst(); <span class=\"hljs-comment\">\/\/ fetch user from database using email address<\/span>\n\n      <span class=\"hljs-keyword\">const<\/span> passwordsMatch = compare(password, user.password); <span class=\"hljs-comment\">\/\/ compare if passwords match<\/span>\n\n      <span class=\"hljs-keyword\">if<\/span> (passwordsMatch) {\n        <span class=\"hljs-keyword\">const<\/span> token = jwt.sign({ email, password }, KEY); <span class=\"hljs-comment\">\/\/ create token<\/span>\n        <span class=\"hljs-comment\">\/\/ save the token as a cookie<\/span>\n        res.setHeader(\n          <span class=\"hljs-string\">\"Set-Cookie\"<\/span>,\n          cookie.serialize(<span class=\"hljs-string\">\"token\"<\/span>, token, {\n            <span class=\"hljs-attr\">httpOnly<\/span>: <span class=\"hljs-literal\">true<\/span>,\n            <span class=\"hljs-attr\">secure<\/span>: <span class=\"hljs-string\">\"development\"<\/span>,\n            <span class=\"hljs-attr\">sameSite<\/span>: <span class=\"hljs-string\">\"strict\"<\/span>,\n            <span class=\"hljs-attr\">maxAge<\/span>: <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">24<\/span> * <span class=\"hljs-number\">7<\/span>, <span class=\"hljs-comment\">\/\/ 1 week<\/span>\n            <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">\"\/\"<\/span>,\n          })\n        );\n        res.json({ <span class=\"hljs-attr\">userId<\/span>: user.id }); <span class=\"hljs-comment\">\/\/ return user ID<\/span>\n      } <span class=\"hljs-keyword\">else<\/span> {\n        res.status(<span class=\"hljs-number\">401<\/span>);\n        <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"Invalid email or password\"<\/span>);\n      }\n    };\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> login;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the&nbsp;<code>login<\/code>&nbsp;function above, the user\u2019s&nbsp;<code>email<\/code>&nbsp;and&nbsp;<code>password<\/code>&nbsp;are destructured from&nbsp;<code>req.body<\/code>. The&nbsp;<code>email<\/code>&nbsp;is used to fetch a user with a matching email from the database. And the passwords are compared using&nbsp;<code>bcrypt.compare()<\/code>, and if the passwords match, a&nbsp;<code>token<\/code>&nbsp;is signed using&nbsp;<code>jwt<\/code>&nbsp;and saved as a&nbsp;<code>cookie<\/code>&nbsp;in the browser. If the passwords don\u2019t match, an&nbsp;<code>error<\/code>&nbsp;is returned.<\/p>\n\n\n\n<p>Navigate to&nbsp;<code>\/pages\/login.js<\/code>&nbsp;and replace the&nbsp;<code>handleOnSubmit<\/code>&nbsp;function with the one below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">      <span class=\"hljs-keyword\">const<\/span> handleOnSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n        e.preventDefault();\n        setLoading(<span class=\"hljs-literal\">true<\/span>);\n        <span class=\"hljs-keyword\">try<\/span> {\n          <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/login\"<\/span>, {\n            <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n            <span class=\"hljs-attr\">headers<\/span>: {\n              <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n            },\n            <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ email, password }),\n          }).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">t<\/span>) =&gt;<\/span> t.json());\n          router.push({\n            <span class=\"hljs-attr\">pathname<\/span>: <span class=\"hljs-string\">\"\/\"<\/span>,\n            <span class=\"hljs-attr\">query<\/span>: {\n              <span class=\"hljs-attr\">userId<\/span>: res?.userId,\n            },\n          });\n        } <span class=\"hljs-keyword\">catch<\/span> (error) {\n          <span class=\"hljs-built_in\">console<\/span>.log(error);\n        }\n        setLoading(<span class=\"hljs-literal\">false<\/span>);\n      };\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Go back to the browser and authenticate the user using the email and password that was used to create the account. If successful, you\u2019ll be redirected to the home page.<\/p>\n\n\n\n<p>Before creating an image, let\u2019s set an&nbsp;<code>isAuthenticated<\/code>&nbsp;prop in the home page, to make sure a user is authenticated. Navigate to&nbsp;<code>\/pages\/index.js<\/code>&nbsp;and add a&nbsp;<code>getServerSideProps<\/code>&nbsp;function below the&nbsp;<code>Home<\/code>&nbsp;component.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getServerSideProps = <span class=\"hljs-keyword\">async<\/span> (context) =&gt; {\n      <span class=\"hljs-keyword\">let<\/span> isAuthenticated;\n      context.req.cookies&#91;<span class=\"hljs-string\">\"token\"<\/span>]\n        ? (isAuthenticated = <span class=\"hljs-literal\">true<\/span>)\n        : (isAuthenticated = <span class=\"hljs-literal\">false<\/span>);\n\n        {<span class=\"hljs-comment\">\/* FETCH IMAGES FROM IMAGES TABLE GOES HERE *\/<\/span>}\n\n      <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">props<\/span>: { isAuthenticated } };\n    };\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the function above, we\u2019re checking if the&nbsp;<code>token<\/code>&nbsp;exists as a cookie in the browser, if it exists,&nbsp;<code>isAuthenticated<\/code>&nbsp;is declared as&nbsp;<code>true<\/code>&nbsp;and it\u2019s sent as a prop to the&nbsp;<code>Home<\/code>&nbsp;component.<\/p>\n\n\n\n<p>Replace the&nbsp;<code>Home<\/code>&nbsp;component with the component below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\">{ isAuthenticated }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n      <span class=\"hljs-keyword\">const<\/span> userId = router.query.userId; <span class=\"hljs-comment\">\/\/ retrieve userId<\/span>\n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"body\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> <span class=\"hljs-attr\">isAuthenticated<\/span>=<span class=\"hljs-string\">{isAuthenticated}<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">{userId}<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Hero<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Images<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n      );\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Refresh the home page in your browser. The next feature we\u2019d implement is to upload a new image as an authenticated user.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#upload-image\"><\/a>Upload Image<\/h2>\n\n\n\n<p>The Cloudinary widget will be used for image upload, so go ahead and create a&nbsp;<a href=\"https:\/\/cloudinary.com\/users\/register_free?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary account<\/a>&nbsp;if you don\u2019t have one.<\/p>\n\n\n\n<p>After creating an account, head on to your Cloudinary dashboard, copy and save your&nbsp;<strong>Cloud Name<\/strong>,&nbsp;<strong>API Key<\/strong>, and&nbsp;<strong>API Secret<\/strong>&nbsp;as environment variables.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--avzZTyGn--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/i.imgur.com\/XkeUflz.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220759\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-8.png\" alt=\"Cloudinary Upload Image\"\/><\/a><\/figure>\n\n\n\n<p>Update&nbsp;<code>.env<\/code>&nbsp;file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">CLOUDINARY_CLOUD_NAME= <span class=\"hljs-string\">'*********************'<\/span>\nCLOUDINARY_API_KEY=<span class=\"hljs-string\">'****************************'<\/span>\nCLOUDINARY_SECRET= <span class=\"hljs-string\">\"****************************\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In your terminal, stop the application from running and run&nbsp;<code>yarn dev<\/code>&nbsp;or&nbsp;<code>npm run dev<\/code>&nbsp;again. This is because we updated our&nbsp;<code>.env<\/code>&nbsp;file.<\/p>\n\n\n\n<p>Now we can utilize the features of Cloudinary in our application. Apart from image upload, we\u2019re going to use AI to automatically tag our images, to do so, follow the steps below:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>On your Cloudinary dashboard, click the&nbsp;<strong>Add-ons<\/strong>&nbsp;tab.<\/li>\n\n\n\n<li>Select the&nbsp;<strong>Google Auto Tagging<\/strong>&nbsp;card.<\/li>\n\n\n\n<li>Click the free plan to subscribe.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--Rqhvmwsw--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/i.imgur.com\/p6aD1x1.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220763\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-9.png\" alt=\"Google Auto Tagging \"\/><\/a><\/figure>\n\n\n\n<p>Add a new file to the&nbsp;<code>\/pages\/api\/<\/code>&nbsp;directory, name it&nbsp;<code>upload.js<\/code>&nbsp;and add the code block below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-comment\">\/\/ \/pages\/api\/upload<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> cookie <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cookie\"<\/span>; <span class=\"hljs-comment\">\/\/ import cookie<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { v2 } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>; <span class=\"hljs-comment\">\/\/ import cloudinary<\/span>\n\n    <span class=\"hljs-comment\">\/\/ CONFIGURE CLOUDINARY<\/span>\n    v2.config({\n      <span class=\"hljs-attr\">cloud_name<\/span>: process.env.CLOUDINARY_CLOUD_NAME,\n      <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n      <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_SECRET,\n    });\n    <span class=\"hljs-keyword\">const<\/span> xata = getXataClient();\n\n    <span class=\"hljs-keyword\">const<\/span> handler = <span class=\"hljs-keyword\">async<\/span> (req, res) =&gt; {\n      <span class=\"hljs-keyword\">var<\/span> cookies = cookie.parse(req.headers.cookie || <span class=\"hljs-string\">\"\"<\/span>);\n      <span class=\"hljs-keyword\">const<\/span> isAuthenticated = cookies.token; <span class=\"hljs-comment\">\/\/ check if user is authenticated<\/span>\n      <span class=\"hljs-keyword\">if<\/span> (!isAuthenticated) {\n        res.status(<span class=\"hljs-number\">401<\/span>).end();\n        <span class=\"hljs-keyword\">return<\/span>;\n      }\n\n      <span class=\"hljs-keyword\">const<\/span> { img, userId } = <span class=\"hljs-built_in\">JSON<\/span>.parse(req.body);\n      <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> v2.uploader.upload(img, { <span class=\"hljs-comment\">\/\/ UPLOAD IMAGE TO CLOUDINARY<\/span>\n        <span class=\"hljs-attr\">categorization<\/span>: <span class=\"hljs-string\">\"google_tagging\"<\/span>, <span class=\"hljs-comment\">\/\/ USE GOOGLE AUTO TAGGING AI<\/span>\n        <span class=\"hljs-attr\">auto_tagging<\/span>: <span class=\"hljs-number\">0.6<\/span>,\n      });\n\n      <span class=\"hljs-keyword\">await<\/span> xata.db.images.create({ <span class=\"hljs-comment\">\/\/ INSERT IMAGE INTO DATABASE<\/span>\n        <span class=\"hljs-attr\">img_url<\/span>: result.secure_url,\n        <span class=\"hljs-attr\">user<\/span>: userId, <span class=\"hljs-comment\">\/\/ user uploading image<\/span>\n        <span class=\"hljs-attr\">tags<\/span>: result.tags,\n      });\n      res.status(<span class=\"hljs-number\">200<\/span>).json(result);\n    };\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> handler;\n\n    <span class=\"hljs-comment\">\/\/ CONFIG TO ALLOW IMAGES UPTO 10mb<\/span>\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config = {\n      <span class=\"hljs-attr\">api<\/span>: {\n        <span class=\"hljs-attr\">bodyParser<\/span>: {\n          <span class=\"hljs-attr\">sizeLimit<\/span>: <span class=\"hljs-string\">\"10mb\"<\/span>,\n        },\n      },\n    };\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code block above, we configure Cloudinary using our cloud name, API key, and secret. After checking if the user is authenticated,&nbsp;<code>img<\/code>&nbsp;and&nbsp;<code>userId<\/code>&nbsp;are destructured from&nbsp;<code>req.body<\/code>, and&nbsp;<code>img<\/code>&nbsp;is uploaded to Cloudinary. As&nbsp;<code>img<\/code>&nbsp;is being uploaded, we add the Goggle Auto Tagging AI config as a second parameter in&nbsp;<code>v2.uploader.upload()<\/code>.<\/p>\n\n\n\n<p>Finally, the image URL, user ID, and tags are inserted into our images table on the Xata database. Navigate to&nbsp;<code>\/components\/Upload.jsx<\/code>&nbsp;and replace the&nbsp;<code>handleOnChange<\/code>&nbsp;function with the code below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">      <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleOnChange<\/span>(<span class=\"hljs-params\">changeEvent<\/span>) <\/span>{\n        <span class=\"hljs-keyword\">const<\/span> reader = <span class=\"hljs-keyword\">new<\/span> FileReader(); <span class=\"hljs-comment\">\/\/ create new reader to read image file as a daat URL<\/span>\n        reader.onload = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">onLoadEvent<\/span>) <\/span>{\n          setImg(onLoadEvent.target.result); <span class=\"hljs-comment\">\/\/ save base64 encoded version of image<\/span>\n        };\n        reader.readAsDataURL(changeEvent.target.files&#91;<span class=\"hljs-number\">0<\/span>]);\n      }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Replace the&nbsp;<code>handleOnSubmit<\/code>&nbsp;function with the code below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">      <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleOnSubmit<\/span>(<span class=\"hljs-params\">event<\/span>) <\/span>{\n        event.preventDefault();\n        setLoading(<span class=\"hljs-literal\">true<\/span>);\n\n        <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/upload\"<\/span>, { <span class=\"hljs-comment\">\/\/UPLOAD IMAGE<\/span>\n          <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n          <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n            <span class=\"hljs-attr\">img<\/span>: img, <span class=\"hljs-comment\">\/\/ send base64 encoded version of image to Cloudinary <\/span>\n            <span class=\"hljs-attr\">userId<\/span>: userId,\n          }),\n        }).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">r<\/span>) =&gt;<\/span> r.json());\n\n        setLoading(<span class=\"hljs-literal\">false<\/span>);\n        onClose(); <span class=\"hljs-comment\">\/\/ close modal<\/span>\n        <span class=\"hljs-keyword\">try<\/span> {\n        } <span class=\"hljs-keyword\">catch<\/span> (error) {\n          <span class=\"hljs-built_in\">console<\/span>.log(error);\n        }\n      }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Go to the browser and click <strong>Upload<\/strong> at the top-right corner to upload an image. You can download free images from&nbsp;<a href=\"https:\/\/unsplash.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Unsplash<\/a>&nbsp;or&nbsp;<a href=\"https:\/\/www.pexels.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pexels<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--JLin5glc--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667735717214_Screenshot%2B2022-11-06%2Bat%2B12.55.04.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220765\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-10.png\" alt=\"new image record\"\/><\/a><\/figure>\n\n\n\n<p>A new image record has been added to the&nbsp;<strong>images<\/strong>&nbsp;table!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#fetch-images\"><\/a>Fetch Images<\/h2>\n\n\n\n<p>We\u2019ve successfully written functions to create a user and create an image. Now let\u2019s query the images table to fetch all images.<\/p>\n\n\n\n<p>Navigate to&nbsp;<code>\/pages\/index.js<\/code>&nbsp;and import&nbsp;<code>getXataClient<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/src\/xata\"<\/span>;\n\nReplace the <span class=\"hljs-string\">`getServerSideProps`<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">with<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">code<\/span> <span class=\"hljs-title\">block<\/span> <span class=\"hljs-title\">below<\/span>:\n\n\n    <span class=\"hljs-title\">export<\/span> <span class=\"hljs-title\">const<\/span> <span class=\"hljs-title\">getServerSideProps<\/span> = <span class=\"hljs-title\">async<\/span> (<span class=\"hljs-params\">context<\/span>) =&gt; <\/span>{\n      <span class=\"hljs-keyword\">let<\/span> isAuthenticated;\n      context.req.cookies&#91;<span class=\"hljs-string\">\"token\"<\/span>]\n        ? (isAuthenticated = <span class=\"hljs-literal\">true<\/span>)\n        : (isAuthenticated = <span class=\"hljs-literal\">false<\/span>);\n\n      <span class=\"hljs-keyword\">const<\/span> xata = getXataClient(); <span class=\"hljs-comment\">\/\/ initialize XataClient<\/span>\n      <span class=\"hljs-comment\">\/\/ Fetch all images with the firstname and lastname of the user that uploads image<\/span>\n      <span class=\"hljs-keyword\">const<\/span> allImages = <span class=\"hljs-keyword\">await<\/span> xata.db.images\n        .select(&#91;<span class=\"hljs-string\">\"*\"<\/span>, <span class=\"hljs-string\">\"user.firstname\"<\/span>, <span class=\"hljs-string\">\"user.lastname\"<\/span>])\n        .getAll();\n      <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">props<\/span>: { allImages, isAuthenticated } };\n    };\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code above, all images were fetched together with the&nbsp;<code>firstname<\/code>&nbsp;and&nbsp;<code>lastname<\/code>&nbsp;of the user that uploads each image.<\/p>\n\n\n\n<p>Add&nbsp;<code>allImages<\/code>&nbsp;as a prop to the&nbsp;<code>Home<\/code>&nbsp;component and pass the prop to the&nbsp;<code>Images<\/code>&nbsp;component.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\">{ allImages, isAuthenticated }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n      <span class=\"hljs-keyword\">const<\/span> userId = router.query.userId;\n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"body\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> <span class=\"hljs-attr\">isAuthenticated<\/span>=<span class=\"hljs-string\">{isAuthenticated}<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">{userId}<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Hero<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Images<\/span> <span class=\"hljs-attr\">allImages<\/span>=<span class=\"hljs-string\">{allImages}<\/span> \/&gt;<\/span> {\/*pass prop*\/}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n      );\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Go to your browser and refresh to view the previously uploaded image.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--qxa-Fxd0--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667738663985_Screenshot%2B2022-11-06%2Bat%2B13.44.04.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220768\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-11.jpg\" alt=\"view uploaded image\"\/><\/a><\/figure>\n\n\n\n<p>Download free images from&nbsp;<a href=\"https:\/\/unsplash.com\/\">Unsplash<\/a>&nbsp;or&nbsp;<a href=\"https:\/\/www.pexels.com\/\">Pexels<\/a>&nbsp;to populate your project, because the next feature we\u2019re implementing will be the search feature.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#search-images\"><\/a>Search Images<\/h2>\n\n\n\n<p>In&nbsp;<code>\/pages\/api\/upload<\/code>&nbsp;we utilized the Google Auto Tagging AI to generate tags for our images during upload, the reason for this is to make search easy for users. If you&nbsp;<code>console.log<\/code>&nbsp;the image you upload to Xata, you\u2019d see a group of tags related to your image.<\/p>\n\n\n\n<p>For example, the image below shows all the tags generated by the AI for the image I uploaded:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--4dL4euAq--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880\/https:\/\/paper-attachments.dropboxusercontent.com\/s_3C3530EA504033CE389C864AD7ED1EF94835B0F736165EFA05A7BAE855016104_1667739113310_Screenshot%2B2022-11-06%2Bat%2B13.46.34.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764220770\/blog-Building_a_Full-Stack_JAMStack_Application_With_Xata_Cloudinary_and_Next.js-12.png\" alt=\"Image tags\"\/><\/a><\/figure>\n\n\n\n<p>If a user searches for any of these words on the website, all images relating to the tag should be fetched. Let\u2019s head on and implement the search functionality.<\/p>\n\n\n\n<p>Add a new file to&nbsp;<code>\/pages\/api\/<\/code>&nbsp;, and name it&nbsp;<code>search.js<\/code>. For the&nbsp;<code>search<\/code>&nbsp;handler, we want to target the tags of each image specifically, Xata can help us generate the code snippet for this functionality. Follow the steps below:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Go to your database dashboard.<\/li>\n\n\n\n<li>Click <strong>Search <\/strong>on the left sidebar.<\/li>\n\n\n\n<li>At the top of the page, click <strong>Target<\/strong>.<\/li>\n\n\n\n<li>Uncheck the&nbsp;<strong>users<\/strong>&nbsp;table.<\/li>\n\n\n\n<li>Uncheck the&nbsp;<strong>img_url<\/strong>&nbsp;and&nbsp;<strong>user<\/strong>&nbsp;columns under the images table.<\/li>\n\n\n\n<li>Click the <strong>&lt;\/&gt; Get code snippet<\/strong>.<\/li>\n\n\n\n<li>Click the&nbsp;<strong>Search by table<\/strong>&nbsp;tag and select <strong>Javascript<\/strong> from the dropdown.<\/li>\n\n\n\n<li>That\u2019s the code snippet for our search functionality.<\/li>\n<\/ul>\n\n\n\n<p>Navigate back to&nbsp;<code>\/pages\/api\/search.js<\/code>, we\u2019ll modify the code snippet generated by Xata. Add the code block below to&nbsp;<code>\/pages\/api\/search.js<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ Generated with CLI<\/span>\n    <span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>; \n    <span class=\"hljs-keyword\">const<\/span> xata = getXataClient();\n\n    <span class=\"hljs-keyword\">const<\/span> handler = <span class=\"hljs-keyword\">async<\/span> (req, res) =&gt; {\n      <span class=\"hljs-keyword\">const<\/span> { searchQuery } = req.body; <span class=\"hljs-comment\">\/\/ get search query<\/span>\n\n      <span class=\"hljs-keyword\">const<\/span> records = <span class=\"hljs-keyword\">await<\/span> xata.search.byTable(searchQuery, {\n        <span class=\"hljs-attr\">tables<\/span>: &#91;\n          { <span class=\"hljs-attr\">table<\/span>: <span class=\"hljs-string\">\"users\"<\/span>, <span class=\"hljs-attr\">target<\/span>: &#91;] },\n          {\n            <span class=\"hljs-attr\">table<\/span>: <span class=\"hljs-string\">\"images\"<\/span>,\n            <span class=\"hljs-attr\">target<\/span>: &#91;{ <span class=\"hljs-attr\">column<\/span>: <span class=\"hljs-string\">\"tags\"<\/span> }],\n          },\n        ],\n        <span class=\"hljs-attr\">fuzziness<\/span>: <span class=\"hljs-number\">0<\/span>,\n        <span class=\"hljs-attr\">prefix<\/span>: <span class=\"hljs-string\">\"phrase\"<\/span>,\n      });\n      res.status(<span class=\"hljs-number\">200<\/span>).json(records);\n    };\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> handler;\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now navigate to&nbsp;<code>\/components\/Images.jsx<\/code>&nbsp;and replace the&nbsp;<code>handleSearch<\/code>&nbsp;function with the one below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">\n      <span class=\"hljs-keyword\">const<\/span> handleSearch = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n        <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/search\"<\/span>, {\n          <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n          <span class=\"hljs-attr\">headers<\/span>: {\n            <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n          },\n          <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n            searchQuery,\n          }),\n        }).then(<span class=\"hljs-function\">(<span class=\"hljs-params\">r<\/span>) =&gt;<\/span> r.json());\n        setImages(result?.images);\n      };\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Refresh your browser and test the search feature. Go to your images table on your Xata dashboard and get a \u201c<strong>tag<\/strong>\u201d to use as a keyword to search.GIF<\/p>\n\n\n\n<p>In the gif above, you\u2019ll notice I have two images with cups in them, when I type the keyword \u201ccup\u201d and click the \u201c<strong>search<\/strong>\u201d button, it returns the two images related to the keyword.<\/p>\n\n\n\n<p>We\u2019re done with the project, now let\u2019s deploy to&nbsp;<a href=\"https:\/\/www.netlify.com\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">Netlify<\/a>!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#deploy-to-netlify\"><\/a>Deploy to Netlify<\/h2>\n\n\n\n<p>Follow the steps below to deploy your site on Netlify:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Go to&nbsp;<a href=\"https:\/\/app.netlify.com\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Netlify<\/a>&nbsp;to create an account if you don\u2019t have one.<\/li>\n\n\n\n<li>Click&nbsp;<strong>Add new site<\/strong>&nbsp;and then click&nbsp;<strong>Import an existing project<\/strong>.<\/li>\n\n\n\n<li>Select your Git provider and authorize Netlify.<\/li>\n\n\n\n<li>Select your project repository.<\/li>\n\n\n\n<li>Click <strong>Show Advanced<\/strong>.<\/li>\n\n\n\n<li>Add all four environment variables from your&nbsp;<code>.env<\/code>&nbsp;file:\n<ul class=\"wp-block-list\">\n<li>XATA_API_KEY<\/li>\n\n\n\n<li>CLOUDINARY_CLOUD_NAME<\/li>\n\n\n\n<li>CLOUDINARY_API_KEY<\/li>\n\n\n\n<li>CLOUDINARY_SECRET<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Click <strong>Deploy site.<\/strong><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Next Steps<\/h2>\n\n\n\n<p>In this tutorial, you built a full-stack JAMStack application using Xata as a serverless database and Cloudinary for media management. Amazing features like the search functionality from Xata and Goggle Auto Tagging AI from Cloudinary were used to make the project more unique. You can learn more and make this project better by adding features like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A profile page to display a user\u2019s details and images they\u2019ve posted.<\/li>\n\n\n\n<li>A settings page where a user can edit their profile details and delete their account.<\/li>\n\n\n\n<li>Ability to like an image and display the number of likes per image.<\/li>\n\n\n\n<li>Ability to delete an image you posted.<\/li>\n\n\n\n<li>Ability to follow a user and display the number of followers.<\/li>\n\n\n\n<li>Explore the Cloudinary Add-ons page and add new add-ons before the image upload.<\/li>\n<\/ul>\n\n\n\n<p>If found this article helpful and want to discuss it in more detail, head over to&nbsp;<a href=\"https:\/\/community.cloudinary.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Community<\/a>&nbsp;forum and its associated&nbsp;<a href=\"https:\/\/discord.gg\/cloudinary\" target=\"_blank\" rel=\"noreferrer noopener\">Discord<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-full-stack-jamstack-application-with-xata-cloudinary-and-nextjs-50pd#resources\"><\/a>Resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/xata.io\/docs\/overview?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Xata Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=3OeEa2cZABM&amp;t=1s\" target=\"_blank\" rel=\"noreferrer noopener\">Colby Fayock&#8217;s YouTube Channel<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>There\u2019s been a surge of interest in serverless cloud products due to their scalability, high transparency, faster performance, cost-effectiveness, instant availability, and operating infrastructure support. Serverless doesn\u2019t mean \u201cno servers.&#8221; It means developers don\u2019t have to manage the servers. Rather, the server is managed by a service provider, and the necessary resources are automatically assigned [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":31470,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[212],"class_list":["post-31466","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-next-js"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js<\/title>\n<meta name=\"description\" content=\"Learn how to build a full-stack JAMStack application using\u00a0Xata,\u00a0Cloudinary, and\u00a0Next.js.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js\" \/>\n<meta property=\"og:description\" content=\"Learn how to build a full-stack JAMStack application using\u00a0Xata,\u00a0Cloudinary, and\u00a0Next.js.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-10-13T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-27T05:27:38+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js\",\"datePublished\":\"2023-10-13T14:00:00+00:00\",\"dateModified\":\"2025-11-27T05:27:38+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\"},\"wordCount\":2170,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA\",\"keywords\":[\"Next.js\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2023\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\",\"name\":\"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA\",\"datePublished\":\"2023-10-13T14:00:00+00:00\",\"dateModified\":\"2025-11-27T05:27:38+00:00\",\"description\":\"Learn how to build a full-stack JAMStack application using\u00a0Xata,\u00a0Cloudinary, and\u00a0Next.js.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js","description":"Learn how to build a full-stack JAMStack application using\u00a0Xata,\u00a0Cloudinary, and\u00a0Next.js.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js","og_locale":"en_US","og_type":"article","og_title":"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js","og_description":"Learn how to build a full-stack JAMStack application using\u00a0Xata,\u00a0Cloudinary, and\u00a0Next.js.","og_url":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2023-10-13T14:00:00+00:00","article_modified_time":"2025-11-27T05:27:38+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js","datePublished":"2023-10-13T14:00:00+00:00","dateModified":"2025-11-27T05:27:38+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js"},"wordCount":2170,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA","keywords":["Next.js"],"inLanguage":"en-US","copyrightYear":"2023","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js","url":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js","name":"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA","datePublished":"2023-10-13T14:00:00+00:00","dateModified":"2025-11-27T05:27:38+00:00","description":"Learn how to build a full-stack JAMStack application using\u00a0Xata,\u00a0Cloudinary, and\u00a0Next.js.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/full-stack-jamstack-application-xata-cloudinary-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Full-Stack JAMStack Application With Xata, Cloudinary, and Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_JAMStack\/Blog-Hackmamba_Building_a_JAMStack.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/31466","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=31466"}],"version-history":[{"count":5,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/31466\/revisions"}],"predecessor-version":[{"id":39462,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/31466\/revisions\/39462"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/31470"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=31466"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=31466"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=31466"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}