Adding images to our recipes is very easy. Change the ArticleForm to accept an image field and make it required so all of our recipes will have one. Like this:
đ Forms/ArticleForm.ts
import { MinLength, Required } from '@Typetron/Validation'
import { Field, Form, Rules } from '@Typetron/Forms'
import { File } from '@Typetron/Storage'
export class ArticleForm extends Form {
@Field()
@Rules(Required, MinLength(5))
title: string
@Field()
image?: File
@Field()
@Rules(Required)
content: string
}
Question: Why does the type of the image field contains the string type also?
Answer: We also type-hinted the image field to string because we will use this form to update articles as well. This means we can also send the image path back to the backend instead of an Image. If an image path is sent back, it means the user didnât change the old image, so we can skip the upload image part of our code. We will take a look at this in the next steps in the tutorial.
Now, change the add method from HomeController to save the image on the disk like so:
đ Controllers/Http/HomeController.ts
import { Controller, Delete, Get, Put, Post } from '@Typetron/Router'
import { ArticleForm } from 'App/Forms/ArticleForm'
import { Storage } from '@Typetron/Storage'
@Controller()
export class HomeController {
@Get()
index() {
return Article.get()
}
@Post()
async add(form: ArticleForm, storage: Storage) {
if (form.image) {
form.image = await storage.save(form.image, 'public/articles')
}
return Article.create(form)
}
@Get(':Article')
read(article: Article) {
return article
}
@Put(':Article')
update(article: Article, form: ArticleForm) {
return article.save(form)
}
@Delete(':Article')
async delete(article: Article) {
await article.delete()
}
}
Using the Storage class we get access to our disk and use it to store our image.
In order to test this we need to send a special kind of data to the server called form-data. You can easily make this type of request from Postman by changing the body type from raw to form-data and fill it with the data we want like in the image below:
đ [POST] /
{
"title": "Cool article",
"content": "Cool article content",
"image": "<imageFile>"
}
If we test by making a POST request, we get back a successful result. But where is the image? If you take a look inside the public/articles directory in your projectâs root folder, you will see a weird named file. Thatâs our image. It just got a weird name, so it wonât conflict with other files that might be in that folder, but other than that is the exact same image weâve uploaded.
All we have to do now is to link the images with the created articles because if you try to make a GET request now, you wonât get any reference to images.
To add images to our articles we just have to add a new property in our Article entity for the image path:
đ Entity/Article.ts
import { Column, CreatedAt, Entity, ID, Options, PrimaryColumn, UpdatedAt } from '@Typetron/Database'
@Options({
table: 'articles',
})
export class Article extends Entity {
@PrimaryColumn()
id: ID
@Column()
title: string
@Column()
content: string
@Column()
image: string
@CreatedAt()
createdAt: Date
@UpdatedAt()
updatedAt: Date
}
After you save the file, Typetron will synchronize the newly added field with the database by running an SQL âALTERâ command.
Letâs make a new article by making again an HTTP Post request, so the field image will get populated in the database. The value for the image column will be the imageâs name including the extension. Now, if we make an HTTP GET request to localhost:8000 we can see our image key with the weird name weâve seen in the public/articles directory.
There is a small issue though. All the other articles donât have an image yet. You can use the edit route to update their image, but before that we need to update the endpoint to upload images too. This will get a bit complicated since we also need to take into account that a user can also send back the originalâs image path back to the server when editing an article. In this case we need to change the ArticleFormâs image property to be of type string | Image:
Since we have two type of our ArticleForm.image property, we need to change our endpoint from HomeController to handle them both:
đ Controllers/Http/HomeController.ts
import { Controller, Delete, Get, Put, Post } from '@Typetron/Router'
import { ArticleForm } from 'App/Forms/ArticleForm'
import { Article } from 'App/Entities/Article'
import { File, Storage } from '@Typetron/Storage'
@Controller()
export class HomeController {
@Get()
index() {
return Article.get()
}
@Post()
async add(form: ArticleForm, storage: Storage) {
if (form.image) {
await storage.save(form.image, 'public/articles')
}
return Article.create(form)
}
@Get(':Article')
read(article: Article) {
return article
}
@Put(':Article')
async update(article: Article, form: ArticleForm, storage: Storage) {
if (form.image) {
await storage.delete(`public/articles/${article.image}`)
await storage.save(form.image, 'public/articles')
}
return article.save(form)
}
@Delete(':Article')
async delete(article: Article) {
await article.delete()
}
}
In order to see images in the browser, we need to activate the static assets serving feature in our app. We can easily do this by going to config/app.ts and add/uncomment the staticAssets property:
đ config/app.ts
/* tslint:disable:no-default-export */
import { AppConfig, DatabaseProvider } from '@Typetron/Framework'
import { RoutingProvider } from 'App/Providers/RoutingProvider'
import { AppProvider } from 'App/Providers/AppProvider'
export default new AppConfig({
port: 8000,
environment: 'development',
middleware: [],
providers: [
AppProvider,
RoutingProvider,
DatabaseProvider
],
staticAssets: {
'': ['public'] // <-- this
}
})
This will allow us to see everything from our public directory. Letâs open one of our images in the browser by making an HTTP GET request to localhost:8000/articles/the-weird-image-name, eg:
đ [GET] /articles/upload_73830303b8e2.jpg
Question: Where does the âarticles/â prefix comes from?
Answer: It comes from this line await storage.save(form.image, âpublic/articlesâ). Here we save our images inside the public/articles directory
Question: Why doesnât the route look like this: localhost:8000/public/articles/image-name?
Answer: This is because the staticAssets setting is set to serve the files from the public directory already.