{"id":28302,"date":"2022-03-23T22:24:00","date_gmt":"2022-03-23T22:24:00","guid":{"rendered":"http:\/\/Handling-image-uploads-with-Postgres-in-RedwoodJS"},"modified":"2025-02-08T12:27:10","modified_gmt":"2025-02-08T20:27:10","slug":"handling-image-uploads-with-postgres-in-redwoodjs","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/","title":{"rendered":"Handling image uploads with PostgreSQL"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><h2>Intro<\/h2>\n<p>There are a lot of new JAMStack frameworks that are appearing on the scene. You have NextJS, Eleventy, React Static, and a bunch of others. One thing that\u2019s missing from these frameworks is the ability to work with databases in your web app.<\/p>\n<p>That\u2019s where RedwoodJS comes in. It\u2019s a framework that lets you do full-stack JAMStack development. It uses React, GraphQL, and Prisma. One of the cool things about this framework is that if you have an existing React project, it\u2019s not terribly complicated to port it over.<\/p>\n<p>This tutorial is just going to give you an idea for one of the things you can do with a full-stack JAMStack application. We\u2019re going to upload images submitted by users to a PostgreSQL database. We\u2019ll go over how to set up your RedwoodJS app and start accepting images from users and displaying them back.<\/p>\n<p>You can clone a working version of this from <a href=\"https:\/\/github.com\/flippedcoder\/redwood-image-uploader\">Github<\/a> or just follow along with the tutorial.<\/p>\n<h2>Creating a RedwoodJS app<\/h2>\n<p>Create a new directory for your app called <code>redwood<\/code>, and go to that folder in a terminal. Then, run the following command.<\/p>\n<p><code>yarn create redwood-app .\/redwood-image-uploader<\/code><\/p>\n<p>This will start a chain of events that creates the skeleton for your entire project, including the back-end. Once the setup process is finished, go to the <code>redwood-image-uploader<\/code> directory in your terminal.<\/p>\n<p>You\u2019ll notice a number of directories and files were created. The api folder is where all of the Primsa and GraphQL files live. The web directory is where the React code lives.<\/p>\n<p>Redwood handles a lot of things behind the scenes for you, but they keep things clear enough to find the right files to edit if you want to do things manually. If you want to learn more about the details, make sure to go <a href=\"https:\/\/redwoodjs.com\/docs\/introduction\">check out their docs<\/a>.<\/p>\n<p>From here, we can actually start our app with the following command.<\/p>\n<p><code>yarn rw dev<\/code><\/p>\n<p>You\u2019ll see the following page once it\u2019s running.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1615427895\/e-603fc55d218a650069f5228b\/tadjffkenaqh2nkhayjq.png\" alt=\"Clean Redwood app\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1042\"\/><\/p>\n<p>Now that the Redwood app is up and running, we can start working on the front-end code.<\/p>\n<h2>Adding an image uploader<\/h2>\n<p>To get started, we\u2019re going to install a few packages in the web directory. We\u2019re going to install the <code>styled-components<\/code> and <code>react-images-upload<\/code> packages. So make sure you\u2019re in the web directory in your terminal, and then run the following.<\/p>\n<p><code>yarn add styled-components react-images-upload<\/code><\/p>\n<p>This gives us an upload component so we don\u2019t have to spend a lot of time handling states and styling. Next, go to the root directory, and run the following.<\/p>\n<p><code>yarn rw generate page home \/<\/code><\/p>\n<p>This creates a bunch of new files that automatically route to a template home page. We\u2019ll be adding all of our code to this HomePage.js file. You can delete all of the existing code in the file, and start fresh by adding a few imports.<\/p>\n<h3>Step 1<\/h3>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" 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> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n<span class=\"hljs-keyword\">import<\/span> ImageUploader <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-images-upload'<\/span>\n<span class=\"hljs-keyword\">import<\/span> styled <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'styled-components'<\/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\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next we\u2019ll start on our HomePage component.<\/p>\n<h3>Step 2<\/h3>\n<p>In our component, we start by creating a new state that will hold the information for the images we want to upload. Then, we return a view with a few components, the main one being the <code>ImageUploader<\/code>.<\/p>\n<p>The props we pass to it let us display the icon to a user, show a preview of the image when it\u2019s uploaded, set some restrictions on files, and start the process of uploading images. The prop we\u2019ll pay the most attention to is what we have in <code>onChange<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" 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> HomePage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> &#91;uploadedPictures, setUploadedPictures] = useState(&#91;])\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Put your pictures here.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>This is important...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ImageUploader<\/span>\n          <span class=\"hljs-attr\">withIcon<\/span>=<span class=\"hljs-string\">{true}<\/span>\n          <span class=\"hljs-attr\">withPreview<\/span>=<span class=\"hljs-string\">{true}<\/span>\n          <span class=\"hljs-attr\">buttonText<\/span>=<span class=\"hljs-string\">\"Choose images\"<\/span>\n          <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(image)<\/span> =&gt;<\/span> onDrop(image)}\n          singleImage={true}\n          imgExtension={&#91;'.jpg', '.gif', '.png', '.gif']}\n          maxFileSize={5242880}\n        \/&gt;\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  )\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> HomePage\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<p>If you run the project again with <code>yarn rw dev<\/code> in the root directory, you should see this when the page loads.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1615428223\/e-603fc55d218a650069f5228b\/su0zaibdltpji6f67mlj.png\" alt=\"Image uploader in the app\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1049\"\/><\/p>\n<h3>Step 3<\/h3>\n<p>Now, we\u2019ll do a little layout clean up with a few styled components. These have CSS that we need to apply to our component.<\/p>\n<pre class=\"js-syntax-highlighted\" 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-keyword\">const<\/span> Button = styled.button<span class=\"hljs-string\">`\n  background-color: #34feac;\n  padding: 10px 12px;\n  border-radius: 20px;\n\n  &amp;:hover {\n    cursor: pointer;\n    background-color: rgba(52, 254, 172, 0.5);\n  }\n`<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> Container = styled.div<span class=\"hljs-string\">`\n  margin: auto;\n  width: 500px;\n`<\/span>\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<h3>Step 4<\/h3>\n<p>Here\u2019s what the updated component should look like. This adds some formatting to the uploader, but the main thing it adds is a submit button for users to save their images in the PostgreSQL database.<\/p>\n<pre class=\"js-syntax-highlighted\" 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> HomePage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> &#91;uploadedPictures, setUploadedPictures] = useState(&#91;])\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Put your pictures here.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>This is important...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      {uploadedPictures.length !== 0 &amp;&amp; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{submitPictures}<\/span>&gt;<\/span>Save your pictures now<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n      )}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Container<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ImageUploader<\/span>\n          <span class=\"hljs-attr\">withIcon<\/span>=<span class=\"hljs-string\">{true}<\/span>\n          <span class=\"hljs-attr\">withPreview<\/span>=<span class=\"hljs-string\">{true}<\/span>\n          <span class=\"hljs-attr\">buttonText<\/span>=<span class=\"hljs-string\">\"Choose images\"<\/span>\n          <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(image)<\/span> =&gt;<\/span> onDrop(image)}\n          singleImage={true}\n          imgExtension={&#91;'.jpg', '.gif', '.png', '.gif']}\n          maxFileSize={5242880}\n        \/&gt;\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Container<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\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<p>Now, we\u2019ll add the functions that are used in the different components.<\/p>\n<h3>Step 5<\/h3>\n<p>The <code>onDrop<\/code> function is how we will store the information for the images we want to upload to the database. It sets the state to an array of images.<\/p>\n<p>The <code>submitPictures<\/code> function is where we\u2019re actually going to upload the image data to PostgreSQL. There are still a few back-end things we need to set up, but this has most of the functionality in place.<\/p>\n<p>When a user submits images to be uploaded to the database, we take all of the images in the <code>uploadedPictures<\/code> state, create new <code>FileReader<\/code> objects for them, and upload the image as a base64 string.<\/p>\n<pre class=\"js-syntax-highlighted\" 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-keyword\">const<\/span> onDrop = <span class=\"hljs-function\">(<span class=\"hljs-params\">picture<\/span>) =&gt;<\/span> {\n    setUploadedPictures(&#91;...uploadedPictures, picture])\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> submitPictures = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    uploadedPictures.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">picture<\/span>) =&gt;<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> reader = <span class=\"hljs-keyword\">new<\/span> FileReader()\n\n      reader.readAsDataURL(picture&#91;<span class=\"hljs-number\">0<\/span>])\n\n      reader.onload = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n        <span class=\"hljs-keyword\">const<\/span> base64Url = reader.result\n      }\n    })\n  }\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<p>Now that we have our front-end mostly finished, let\u2019s switch to some back-end business.<\/p>\n<p>\n<strong><span style=\"text-decoration:underline;\">Storing Images as BLOBs in PostgreSQL<\/span><\/strong>\n<\/p>\n<p>\nAnother approach to storing images in a PostgreSQL database is by using Binary Large Objects (BLOBs). This method involves converting the image file into a binary format and then storing it directly in the database.\n<\/p>\n<p>\nFor those who might be using Java in their backend, here&#8217;s a simple way to insert an image:\n<\/p>\n<pre class=\"prettyprint\">File file = new File(\"myimage.gif\"); FileInputStream fis = new FileInputStream(file); PreparedStatement ps = conn.prepareStatement(\"INSERT INTO images VALUES (?, ?)\");\n<\/pre>\n<p>\nAdditionally, if you&#8217;re considering storing images in a separate database, PostgreSQL offers the DBlink functionality. This allows you to connect and run queries across different PostgreSQL databases. It can be particularly useful if you want to segregate your image storage from other data.\n<\/p>\n<p>\nAnother useful datatype in PostgreSQL for image storage is bytea (BYTE Array), which can be handy for caching thumbnail images. This datatype allows for the storage of binary strings, making it suitable for smaller images or thumbnails.\n<\/p>\n<h3>Setting up PostgreSQL<\/h3>\n<p>If you don\u2019t have PostgreSQL installed locally, you can download it for free here: <a href=\"https:\/\/www.postgresql.org\/download\/\">https:\/\/www.postgresql.org\/download\/<\/a> This will also work if you have an instance of PostgreSQL in the cloud.<\/p>\n<p>In the <code>api<\/code> directory, update the <code>schema.prisma<\/code> to use <code>postgresql<\/code> instead of <code>sqlite<\/code> for the provider. Then, you can delete the <code>UserExample<\/code> model, and add the following.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model Picture {\n  id        Int    @id @<span class=\"hljs-keyword\">default<\/span>(autoincrement())\n  file      <span class=\"hljs-built_in\">String<\/span>\n  file_name <span class=\"hljs-built_in\">String<\/span>\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<p>You\u2019ll need to rename the <code>.env.example<\/code> file in the root directory to <code>.env<\/code>. Then edit it to uncomment the <code>DATABASE_URL<\/code>. This is where you put your connection string to the PostgreSQL database. It might look something like this: <code>postgres:\/\/postgres:admin@localhost:5432\/pictures<\/code><\/p>\n<p>Now, you\u2019ll need to run a migration to get the table in place in PostgreSQL. Do that with the following command in the root directory.<\/p>\n<p><code>yarn rw prisma migrate dev<\/code><\/p>\n<p>You\u2019ll need to write a name for the migration at some point in the process, so make sure it describes what you\u2019re migrating. Once your migration is finished, you can go to the pgAdmin for your PostgreSQL instance, and you should see something similar to this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1615428772\/e-603fc55d218a650069f5228b\/g4fll1ie1bhuhpxmprta.png\" alt=\"PostgreSQL instance with pictures table\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"834\"\/><\/p>\n<h3>Working with GraphQL<\/h3>\n<p>We have our front-end and database set up, so now we need to handle the back-end part with GraphQL. This is something else that Redwood makes super fast to do. If you run <code>yarn rw g sdl pictures --crud<\/code>, it will generate all of the GraphQL types, queries, and mutations based the pictures schema you defined in the <code>schema.prisma<\/code> file.<\/p>\n<p>You can take a look at the generated files in the <code>api\/src\/graphql<\/code> and <code>api\/src\/services<\/code> directories. The <code>graphql<\/code> directory has all of your types defined, and the services directory has all of your mutations and queries defined. Now, all we have to do is add these methods to the front-end and we\u2019ll be finished!<\/p>\n<p>First, we\u2019ll go through each of the autogenerated files just so you know where everything is defined.<\/p>\n<h4>pictures.sdl.js<\/h4>\n<p>This file was autogenerated as part of the <code>yarn rw g sdl pictures --crud<\/code> command. This is the GraphQL schema that is based on the model we defined in the <code>schema.prisma<\/code> file. Redwood creates all of the types we need to do all of the basic CRUD operations, but we\u2019ll be focusing on the create (mutation) and read (query) operations.<\/p>\n<pre class=\"js-syntax-highlighted\" 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> schema = gql<span class=\"hljs-string\">`\n  type Picture {\n    id: Int!\n    file: String!\n    file_name: String!\n  }\n\n  type Query {\n    pictures: &#91;Picture!]!\n    picture(id: Int!): Picture\n  }\n\n  input CreatePictureInput {\n    file: String!\n    file_name: String!\n  }\n\n  input UpdatePictureInput {\n    file: String\n    file_name: String\n  }\n\n  type Mutation {\n    createPicture(input: CreatePictureInput!): Picture!\n    updatePicture(id: Int!, input: UpdatePictureInput!): Picture!\n    deletePicture(id: Int!): Picture!\n  }\n`<\/span>\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<h4>pictures.js<\/h4>\n<p>Another nice thing Redwood does for us is autogenerate all of the GraphQL resolvers for the CRUD operations. These connect directly to the PostgreSQL instance you defined in the <code>.env<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" 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\">import<\/span> { db } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'src\/lib\/db'<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> pictures = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> db.picture.findMany()\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> picture = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ id }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> db.picture.findUnique({\n    <span class=\"hljs-attr\">where<\/span>: { id },\n  })\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> createPicture = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ input }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> db.picture.create({\n    <span class=\"hljs-attr\">data<\/span>: input,\n  })\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> updatePicture = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ id, input }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> db.picture.update({\n    <span class=\"hljs-attr\">data<\/span>: input,\n    <span class=\"hljs-attr\">where<\/span>: { id },\n  })\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> deletePicture = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ id }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> db.picture.delete({\n    <span class=\"hljs-attr\">where<\/span>: { id },\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<h3>Finishing the front-end<\/h3>\n<p>Back in our <code>HomePage<\/code> component, we need to add the calls to GraphQL that will let us save images uploaded by users, and reload them on the page. To do that, we\u2019ll start by adding the following import statement.<\/p>\n<h4>Step 1<\/h4>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" 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> { useMutation, useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/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<p>Next we need to define how we want the data returned from GraphQL. We\u2019re using the GraphQL language to define our query and how we want the picture data returned with <code>GET_PICTURE<\/code>.<\/p>\n<p>In <code>CREATE_PICTURE<\/code>, we\u2019re defining how we should pass data to the database, and what values should be returned after a successful creation.<\/p>\n<h4>Step 2<\/h4>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> GET_PICTURE = gql`\n  query {\n    pictures {\n      id\n      file\n      file_name\n    }\n  }\n`\n<span class=\"hljs-keyword\">const<\/span> CREATE_PICTURE = gql`\n  mutation createPicture($file: String, $file_name: String) {\n    createPicture(input: { file: $file, file_name: $file_name }) {\n      id\n      file_name\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\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h4>Step 3<\/h4>\n<p>Finally, we\u2019ll use our imported methods to help us work with these GraphQL queries. This goes inside of the <code>HomePage<\/code> component below our state.<\/p>\n<p>The <code>create<\/code> method is what we\u2019ll be using to upload pictures to the database and the data object will give us the information we need to display saved images to users.<\/p>\n<pre class=\"js-syntax-highlighted\" 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-keyword\">const<\/span> &#91;create] = useMutation(CREATE_PICTURE)\n<span class=\"hljs-keyword\">const<\/span> { data } = useQuery(GET_PICTURE)\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<p>Now we need to update the <code>submitPictures<\/code> function to use the <code>create<\/code> method. When a user clicks this button now, it will upload their image to the database in base64 format and reload the page to display the saved images.<\/p>\n<pre class=\"js-syntax-highlighted\" 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\">const<\/span> submitPictures = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    uploadedPictures.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">picture<\/span>) =&gt;<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> reader = <span class=\"hljs-keyword\">new<\/span> FileReader()\n\n      reader.readAsDataURL(picture&#91;<span class=\"hljs-number\">0<\/span>])\n\n      reader.onload = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n        <span class=\"hljs-keyword\">const<\/span> base64Url = reader.result\n\n        create({ <span class=\"hljs-attr\">variables<\/span>: { <span class=\"hljs-attr\">file<\/span>: base64Url, <span class=\"hljs-attr\">file_name<\/span>: picture&#91;<span class=\"hljs-number\">0<\/span>].name } })\n\n        location.reload()\n      }\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<h4>Quick check<\/h4>\n<p>This is what your <code>HomePage<\/code> component should look like now.<\/p>\n<p><a href=\"https:\/\/gist.github.com\/flippedcoder\/b11b39719130d6f5eb3922fc03885192\">GitHub Gist with updated code<\/a><\/p>\n<h3>Cleaning things up<\/h3>\n<p>The last thing we need to do is show saved images to users. We\u2019ll do that by using the <code>data<\/code> object. We\u2019ll create a couple of styled components to make things a little clearer in the view.<\/p>\n<pre class=\"js-syntax-highlighted\" 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\">const<\/span> Flex = styled.div<span class=\"hljs-string\">`\n  display: flex;\n`<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> Img = styled.img<span class=\"hljs-string\">`\n  padding: 24px;\n  height: 200px;\n  width: 200px;\n`<\/span>\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<p>Now we can loop through the saved images, and display them with the following addition to the component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Flex<\/span>&gt;<\/span>\n    {data?.pictures &amp;&amp;\n      data.pictures.map((picture) =&gt; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{picture.file}<\/span> \/&gt;<\/span>)}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Flex<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Conclusion<\/h2>\n<p>We\u2019re done! Now, when a user uploads an image through this interface, you\u2019ll be able to store and retrieve it from a Postgres database. Hopefully this has given you an idea of how to work with a full-stack JAMStack app using Redwood. There are a number of other ways you can handle images as well. You could upload them to S3 buckets, or use a service like Cloudinary.<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/redwoodjs.com\/docs\/introduction\">RedwoodJS documentation<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/www.prisma.io\/docs\/\">Prisma documentation<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/graphql.org\/learn\/\">GraphQL documentation<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28303,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[379,134,370,175,246,371],"class_list":["post-28302","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-graphql","tag-guest-post","tag-image","tag-jamstack","tag-react","tag-under-review"],"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>Handling image uploads with PostgreSQL<\/title>\n<meta name=\"description\" content=\"Handling image files in a JAMStack application can be tricky. You have to decide whether you want to upload them to your own database, use a third party, or store them in another way. This article will go over how you can upload images to a PostgreSQL database using the RedwoodJS JAMStack framework.\" \/>\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\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Handling image uploads with PostgreSQL\" \/>\n<meta property=\"og:description\" content=\"Handling image files in a JAMStack application can be tricky. You have to decide whether you want to upload them to your own database, use a third party, or store them in another way. This article will go over how you can upload images to a PostgreSQL database using the RedwoodJS JAMStack framework.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:24:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-02-08T20:27:10+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Handling image uploads with PostgreSQL\",\"datePublished\":\"2022-03-23T22:24:00+00:00\",\"dateModified\":\"2025-02-08T20:27:10+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\"},\"wordCount\":5,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA\",\"keywords\":[\"GraphQL\",\"Guest Post\",\"Image\",\"JAMStack\",\"React\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\",\"name\":\"Handling image uploads with PostgreSQL\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:24:00+00:00\",\"dateModified\":\"2025-02-08T20:27:10+00:00\",\"description\":\"Handling image files in a JAMStack application can be tricky. You have to decide whether you want to upload them to your own database, use a third party, or store them in another way. This article will go over how you can upload images to a PostgreSQL database using the RedwoodJS JAMStack framework.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA\",\"width\":6048,\"height\":4024},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Handling image uploads with PostgreSQL\"}]},{\"@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\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Handling image uploads with PostgreSQL","description":"Handling image files in a JAMStack application can be tricky. You have to decide whether you want to upload them to your own database, use a third party, or store them in another way. This article will go over how you can upload images to a PostgreSQL database using the RedwoodJS JAMStack framework.","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\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/","og_locale":"en_US","og_type":"article","og_title":"Handling image uploads with PostgreSQL","og_description":"Handling image files in a JAMStack application can be tricky. You have to decide whether you want to upload them to your own database, use a third party, or store them in another way. This article will go over how you can upload images to a PostgreSQL database using the RedwoodJS JAMStack framework.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:24:00+00:00","article_modified_time":"2025-02-08T20:27:10+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/"},"author":{"name":"","@id":""},"headline":"Handling image uploads with PostgreSQL","datePublished":"2022-03-23T22:24:00+00:00","dateModified":"2025-02-08T20:27:10+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/"},"wordCount":5,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA","keywords":["GraphQL","Guest Post","Image","JAMStack","React","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/","name":"Handling image uploads with PostgreSQL","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA","datePublished":"2022-03-23T22:24:00+00:00","dateModified":"2025-02-08T20:27:10+00:00","description":"Handling image files in a JAMStack application can be tricky. You have to decide whether you want to upload them to your own database, use a third party, or store them in another way. This article will go over how you can upload images to a PostgreSQL database using the RedwoodJS JAMStack framework.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA","width":6048,"height":4024},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/handling-image-uploads-with-postgres-in-redwoodjs\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Handling image uploads with PostgreSQL"}]},{"@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":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924875\/Web_Assets\/blog\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717\/2cf690c6cbc65f966016f87339e83bcbf79dc278-6048x4024-1_28303a7717.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28302","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\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28302"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28302\/revisions"}],"predecessor-version":[{"id":36721,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28302\/revisions\/36721"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28303"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28302"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28302"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28302"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}