Now the image should be stored
on the file system, not in the database
because storing files in databases is a bad idea.
It's bad for performance
because databases simply aren't built for that.
Instead, I wanna store the uploaded files
in that public folder because any images stored there
in that images folder will be publicly available
so they can be rendered on the screen without problems.
And therefore we'll now need to perform a couple of steps.
First, I'll start by getting the extension
of the uploaded image
because it could be a JPEG or PNG file.
So I'll dig into the meal image.
Keep in mind that we're storing that image,
which we got from the form under that image key.
So I'm getting access to that here.
And then on that I'll call split to split it on the dot
and pop the last element, which will be the file extension.
Next, I also want to generate a unique file name
and not use the file name of the user.
So therefore here what I'll do
is I'll generate a string with those tactics
using dot string template literal notation,
which we can use in JavaScript
to create a file name that uses .slug here.
So meal.slug actually, and then a dot,
and then the extension.
So now with that we got the file name.
Now we need to write that to a file in that public folder.
And we can do that with help of an API
provided by node JS.
To be precise, the file system API.
And for that I'll import fs from node:fs.
This allows us to work with the file system.
We can then use this fs module to call createWriteStream,
which will create a stream that allows us
to write data to a certain file.
Now createWriteStream then needs a path to default,
which you wanna write.
And it'll then return a stream object
which you can then use to write to that path.
So regarding that path, I'll create a dynamic string here
and I'll target public/images.
So this images folder here in this public folder,
/ and then the fileName
to which you wanna write must be included in that path.
So we can inject that fileName here,
this fileName which we constructed earlier.
With that, we then get a stream which we can store
in a constant or variable
in which we can use to write to that path.
So to that image in that path.
Now we can write to that stream
by calling the write method on it.
And this write method then wants a chunk.
Now what's that chunk?
In case of our image here,
it means that we should convert the image
to a so-called buffer to a bufferedImage you could say,
which might sound fancy,
but which thankfully isn't too difficult to do.
You can use that image object, which we have here,
which we're getting from our form after all.
So that image that's being stored under that image key.
And that object has an arrayBuffer method you can call,
which will give you such a buffer,
which we need here for the write method.
The only tricky thing here is that arrayBuffer
actually will give you a promise
that eventually resolves to that buffer
and therefore we must await this here.
And in order to use await here, we should add async
in front of the saveMeal function here.
And with that, we then got this bufferedImage.
Now this is of type arrayBuffer
and write actually wants a regular buffer.
So therefore here we have to call Buffer from
and past this arrayBuffer
so this bufferedImage to it, like this.
By the way, one other little error I just spotted is here
where I split my image name to get the extension.
Here, we have to access meal.image.name.split
because meal.image is simply that image
which we get from the form so that image object
that's automatically generated by the browser,
and that object will indeed
have a couple of helpful properties.
One of them is the name property,
which carries the name of the image file
that was uploaded.
So that's something we should change here.
But with that back to the stream,
now we're writing this buffer to this path,
including this file
and this write method now also takes a second argument.
The first argument is the thing you wanna write.
The second argument is a function that will be executed
once it's done writing.
And here we get an error argument,
which is null if everything worked,
but which will hold some error information
if something went wrong.
So for here, we should check if error is truthy.
So if we have an error,
and in that case I wanna throw a new Error
where I say, Saving image failed, or something like this.
And with that we wrote the image to this file.
Now we just need to store the overall data in the database.
And for that I'll start by overriding the image
that's stored in my meal object with a path to the image
where we stored it.
Because I don't wanna store the image file itself
in a database, databases are not built for that,
instead I just wanna store the path.
Hence I'll overwride the image object in my meal
with that path here,
though there you should actually remove this public segment
because all requests for images will be sent
to the public folder automatically anyways.
Or put in other words, the content of the public folder
will be served as if it were served on the root level
of your Server anyways
and therefore public shouldn't be included here
so that it won't be included later
when requests for that image will be sent.
But with that, we now finished the preparations
for this meal object
and we can now save it in a database.
And to save it, I'll use my db object
and then prepare another statement.
And here I'll prepare a statement
and I'll use this template literal notation
to split it across multiple lines
where I wanna insert some data into the meals table
and I wanna insert data into the following fields,
into the title, into the summary field,
into the instructions field, into the creator field
and the creator email field
and into the image and slug fields.
So basically into all fields that we configured
when we created the table except for the id,
because that will be populated automatically.
Now we must specify the values
that should be inserted into those fields.
And here you could directly inject those values,
but this is not recommended
because that approach would be vulnerable
to SQL injection attacks.
Instead, you should use those placeholders
one for every field though to make this a bit easier,
you can also use another syntax here
supported by better-sqlite,
which I also used in the initdb.js file, this syntax here.
You can target specific fields by their name, like this.
And then later just pass an object to the run function,
which we will call on this prepared statement
and better-sqlite.
The package we're using to execute this command here
will then look at those property names
in that object you're passing to it
to extract the values stored under those property names
and it will then use those values
to store them in those fields.
However, you must make sure that the order you got here
is the same as you have it here
and therefore I'll reorder that
and put title first, then the summary,
then the instructions, then the creator,
then the creator email, and then the image and the slug.
I also will remove that trailing comma
and instead add one here.
Getting those commas right is really important.
You will get errors if you don't do it.
Well, and then we can call run on that statement
and pass our meal object to it.
And thanks to this syntax we're using here,
the data for those fields will automatically be extracted.
And that's it then.
This is a saveMeal function
that will store both a file
and the data in the database.
With that done, we can go back to our Server Action
and there, instead of console logging that meal,
we can call saveMeal.
So we can call this function on which we just worked.
Now saveMeal will return a promise,
so we can also add async here
and then use a wait here.
Though at the moment, I then don't have any follow-up action
that should be executed here, but that will come later.
With that though, we're making sure to call saVeMeal here
in the Server Action.
Therefore, now we should pass the meal to saveMeal.
And if we now save everything
and you got that development server up and running,
if you go to that share meal page,
you should be able to enter some data here,
like this and then pick an image
and click Share Meal.
And with that, nothing happens here, which is a good sign.
At least we got no error.
But of course we also don't know whether it worked.
Now we'll take care of that in a second.
For the moment, we can take a look at the public folder,
and there indeed, I find my uploaded image.
So that's a good hint that it seemed to have worked.
I also got no error here in my terminal.
And if we go back to the page
and browse our meals again,
we should also find our meal here.
And that therefore proves that it did work.
Now to provide a better user experience,
we might want to redirect the user
once the meal data has been submitted.
And for that we can go back to our actions
and hereafter a saveMeal.
We can call redirect
and import that from next navigation.
This redirect function will do what its name implies.
It will redirect the user to a different page.
For that, you have to pass a path to redirect,
and that of course should be the path
of the page you wanna redirect the user to.
So for example, to /meals.
With that added, if we reload our page here maybe
and browse all those meals that we got there,
and I then add a new meal.
And here I'll just quickly enter some dummy data
to save some time.
I'll use the same image though
because we'll use the title as a slug
for the image name on the Server anyways,
so we won't override the old image.
But if I do all that and I then click Share Meal,
initially, still nothing happens.
We'll take care of that soon.
But after a short while, we are redirected
and I see my new meal here, so that all works.