- video: https://app.rocketseat.com.br/node/iniciando-back-end-do-app/group/upload-de-imagens/lesson/upload-de-arquivos-1
- commit: https://github.com/rocketseat-education/bootcamp-gostack-modulos/commit/9f58d620b759c6ef4783f913bf766ceab9cceebd#diff-2efc37c87c194d03fc0dadbef51f8814
- goals:
- create a new column in the users table to save the path to the avatar file.
- get an image file from the user and save it in the server
yarn add multer
yarn add -D @types/multer
Create src/config/upload.ts
(note: it's going to be changed later):
import path from 'path';
import crypto from 'crypto';
import multer from 'multer';
export default {
storage: multer.diskStorage({
destination: path.resolve(__dirname, '..', '..', 'tmp'),
filename(request, file, callback) {
const fileHash = crypto.randomBytes(10).toString('HEX');
const fileName = `${fileHash}-${file.originalname}`;
return callback(null, fileName);
},
}),
};
Receiving the file in src/routes/users.routes.ts
:
import multer from 'multer';
import uploadConfig from '../config/auth';
const uploadMiddleware = multer(uploadConfig);
// ...
usersRouter.patch(
'/avatar',
ensureAuthenticated,
uploadMiddleware.single('avatar'),
async (request, response) => {
return response.json({ ok: true });
},
);
Test with insomnia and check if the image files are being saved in the /tmp
directory.
Create a migration to add the avatar
column in the users table:
yarn typeorm migration:create -n AddAvatarColumnToUsers
src/migrations/*AddAvatarColumnToUsers
:
// ...
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'users',
new TableColumn({
name: 'avatar',
type: 'varchar', // although you can, do NOT save raw image in the DB
isNullable: true,
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('users', 'avatar');
}
Add the avatar column in the model too (it's not mentioned in this lecture's video).
src/models/User.ts
:
// ...
class User {
// ...
@Column()
avatar: string
// ...
}
Run the migration.
yarn typeorm migration:run
- video: https://app.rocketseat.com.br/node/iniciando-back-end-do-app/group/upload-de-imagens/lesson/atualizando-avatar
- commit: https://github.com/rocketseat-education/bootcamp-gostack-modulos/commit/ad50714af3938d02c36888b9c2e4f23038ce3723#diff-2efc37c87c194d03fc0dadbef51f8814
- goals:
- save the path to the avatar file in the database
Create src/services/UpdateUserAvatarService.ts
to do this:
- get the user data from the db (throw error if not found)
- if user's avatar is not null, delete the old file
- set the path to the new file as
user.avatar
Note: the userRepository.findOne()
accepts the user ID as argument.
Note: I noticed the method used in the video to check if a file exists is kinda buggy. Then I used this:
if (fs.existsSync(userAvatarFilePath)) {
await fs.promises.unlink(userAvatarFilePath);
}
My complete source of src/services/UpdateUserAvatarService.ts
:
import { getRepository } from 'typeorm';
import path from 'path';
import fs from 'fs';
import uploadConfig from '../config/upload';
import User from '../models/User';
interface Request {
userId: string;
avatarFilename: string;
}
class UpdateUserAvatarService {
public async execute({ userId, avatarFilename }: Request): Promise<User> {
const usersRepository = getRepository(User);
const user = await usersRepository.findOne(userId);
if (!user) {
throw new Error('Only authenticated user can change avatar');
}
if (user.avatar) {
const userAvatarFilePath = path.join(uploadConfig.directory, user.avatar);
if (fs.existsSync(userAvatarFilePath)) {
await fs.promises.unlink(userAvatarFilePath);
}
}
user.avatar = avatarFilename;
await usersRepository.save(user);
return user;
}
}
export default UpdateUserAvatarService;
In the src/routes/users.routes.ts
:
usersRouter.patch(
'/avatar',
ensureAuthenticated,
uploadMiddleware.single('avatar'),
async (request, response) => {
try {
const updateUserAvatar = new UpdateUserAvatarService();
const user = await updateUserAvatar.execute({
user_id: request.user.id, // ensureAuthenticated middlware filled this
avatarFilename: request.file.filename, // uploadMiddleware filled this
});
return response.json(user);
} catch(err) {
return response.status(400).json({ error: err.message });
}
},
);
Test with insomnia and check if:
- the path to the image file was persisted in the database
- the old file was deleted (if it's an update).
- video: https://app.rocketseat.com.br/node/iniciando-back-end-do-app/group/upload-de-imagens/lesson/servindo-arquivos-estaticos
- commit: https://github.com/rocketseat-education/bootcamp-gostack-modulos/commit/b5e35814a288d833f10dd167f1c650217c3a1a87#diff-2efc37c87c194d03fc0dadbef51f8814
- goals:
- serve the image file
src/server.ts
:
// ...
import uploadConfig from './config/upload';
// ...
app.use('/files', express.static(uploadConfig.directory));
// ...
- handle file uploads
- a route for
POST /users/avatar
receiving an image file - install and use multer to create a middleware to get the file
- save the file in the
/tmp
directory.
- save the file in the
- add the column avatar to the users table and User model.
- create a migration to add the avatar column in the users table
- add the avatar column in the User model
- on upload:
- delete the current file in
user.avatar
- save the file path to the recently uploaded file in
user.avatar
https://github.com/meleu/gobarber/tree/fc1c76b7b16fdab909657320ff48c17ae7cc10c5