강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

hib4888님의 프로필 이미지
hib4888

작성한 질문수

[리뉴얼] React로 NodeBird SNS 만들기

express.static 미들웨어

src참조 오류 관련 질문드리겠습니다.

해결된 질문

작성

·

360

0

ADD_POSTING_SEUCCESS후에 다음과 같이 정상적으로 post가 등록됬습니다.

localhost_3060 - Chrome 2022-09-25 오후 8_21_32.png

근데 아래와 같이 클라이언트에서 포스트의 이미지 주소를 참조하지 못하고 있다고 에러가 발생해서 질문드립니다.

localhost_3060 - Chrome 2022-09-25 오후 8_29_47.pngpost.js - prepare - Visual Studio Code [Administrator] 2022-09-25 오후 8_45_26 (2).png

express static에서 문제가 발생한것같은데 혹시 해당 오류 원인에 대해서 알 수 있을까요?

postcard.js

 const PostCard = ({ post }) => {
  const dispatch = useDispatch();
  const id = useSelector((state) => state.user.me && state.user.me.id);  
  const liked = post.Likers.find((v) => v.id === id);
  
  return (    
    <article>
      <CardWrapper
        bodyStyle={{height: '120px', overflow: 'hidden'}}
        hoverable
        cover={
          <CardImageWrapper>
            <ImageWrapper alt="post image" src={`http://localhost3065/${post.Images[0].src}`} onClick={showPostModal} />      
          </CardImageWrapper>
        }
                         :
                         :

export default PostCard;

postingform.js

import React, { useCallback } from 'react';
import { Button, Form, Input, Upload } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { useDispatch, useSelector } from 'react-redux';
import Router from 'next/router';

import { PostingFormWrapper, FormWrapper, FormHeader, HeaderText, HeaderBtn, HeaderDiviver, ImageUploaderWrapper, ContentFormWrapper, TagsInputWrapper } from './styles';
import { ADD_POST_REQUEST, UPLOAD_IMAGES_REQUEST } from '../../reducers/post';

const PostingForm = () => {  
  const { imagePaths, addPostLoading } = useSelector((state) => state.post);
  const dispatch = useDispatch();
  const onSubmitForm = useCallback((value) => {
    console.log(value);
    dispatch({
      type: ADD_POST_REQUEST,
      data: {
        imagePaths,
        content: value,
      }
    })       
  
  }, [imagePaths]);

  const normFile = useCallback((e) => {
    console.log(`normFile : ${e}`);
  
    if (Array.isArray(e)) {
      return e;
    }
  
    return e?.fileList;
  }, []);

  const onChangeImages = useCallback((e) => {        
    console.log(`onchange : ${e}`);    
    const imageFormData = new FormData();
    
    e.fileList.forEach((f) => {
      imageFormData.append('image', f.originFileObj);
    });  
    
    dispatch({
      type: UPLOAD_IMAGES_REQUEST,
      data: imageFormData,
    });    
  }, []); 

  // const onRemoveImages = useCallback((e) => {
  //   console.log(`onRemove : ${e.name}`);    
  // }, []);

  const onBeforeUpload = useCallback((file, fileList) => {
     // Return False So That Antd Doesn't Upload The Picture Right Away
    
    return false
  }, []);

  return (
    <section>
      <PostingFormWrapper
        name="posting"
        onFinish={onSubmitForm}      
        scrollToFirstError
        encType='multipart/form-data'
      >
        <FormWrapper style={{marginBottom: '1em'}}>
          <FormHeader>
            <HeaderText>Post Writing</HeaderText>
            <div>
              <HeaderBtn 
                type='primary' 
                size='large' 
                htmlType="submit"
                loading={addPostLoading}
              >
                등록
              </HeaderBtn>
              <Button size='large'>취소</Button>
            </div>
          </FormHeader>
          <HeaderDiviver />
        </FormWrapper>

        <FormWrapper
          name="title"
          rules={[
            {
              type: 'text',
            },
            {
              required: true,
              message: '포스팅 제목을 입력하세요.',
            },
          ]}
          hasFeedback
        >
          <Input placeholder='제목을 입력해 주세요.' allowClear="true" size='large' />
        </FormWrapper>

        <FormWrapper
          name="desc"
          rules={[
            {
              type: 'text',
            },            
          ]}          
        >
          <Input placeholder='포스팅의 간략한 설명을 입력해 주세요.' allowClear="true" size='large' />
        </FormWrapper>

        <ImageUploaderWrapper
          name="images"
          rules={[          
            {
              required: true,
              message: '조리사진을 첨부하세요.',
            },
          ]}
          valuePropName="fileList"
          getValueFromEvent={normFile}
        >
          {/* action: 파일을 업로드할 실제 URL -> localhost3065/images */}
          <Upload.Dragger 
            name="image" 
            multiple
            // action="http://localhost:3065" 
            listType="picture"
            onChange={onChangeImages}
            // onRemove={onRemoveImages}
            beforeUpload={onBeforeUpload}
          >            
            <p style={{marginBottom: '0.5em'}}>Drag files here OR</p>            
            <Button type='primary' size='large' icon={<UploadOutlined />}>Upload</Button>
          </Upload.Dragger>
        </ImageUploaderWrapper>
        
        <ContentFormWrapper
          name="ingredient"
          rules={[
            {
              type: 'text',
            },
            {
              required: true,
              message: '재료를 입력하세요.',
            },
          ]}
          hasFeedback
        >
          <Input.TextArea 
            placeholder='재료를 입력하세요.' 
            size='large' 
            showCount
            maxLength={100}
            rows={5}
          />
        </ContentFormWrapper>

        <ContentFormWrapper
          name="recipes"
          rules={[
            {
              type: 'text',
            },
            {
              required: true,
              message: '요리방법을 입력하세요.',
            },
          ]}
          hasFeedback
        >
          <Input.TextArea 
            placeholder='요리방법을 입력하세요.' 
            size='large' 
            showCount
            maxLength={1000}
            rows={20}
          />
        </ContentFormWrapper>

        <ContentFormWrapper
          name="tips"
          rules={[
            {
              type: 'text',
            },          
          ]}        
        >
          <Input.TextArea 
            placeholder='Tip을 입력하세요.' 
            size='large' 
            showCount
            maxLength={200}
            rows={8}
          />
        </ContentFormWrapper>

        <Form.Item
          name="tags"
            rules={[
              {
                type: 'text',
              },            
            ]}          
          >
          <TagsInputWrapper placeholder='태그를 입력해 주세요.' size='large' />
          
        </Form.Item>        
      </PostingFormWrapper>    
    </section>    
  )
};

export default PostingForm;

reducer

const reducer = (state = initialState, action) => {
  return produce(state, (draft) => {
    switch (action.type) {    
      case UPLOAD_IMAGES_REQUEST:
        draft.uploadImagesLoading = true;
        draft.uploadImagesDone = false;
        draft.uploadImagesError = null;
        break;
      case UPLOAD_IMAGES_SUCCESS:        
        draft.imagePaths = action.data;
        draft.uploadImagesLoading = false;
        draft.uploadImagesDone = true;        
        break;
      case UPLOAD_IMAGES_FAILURE:
        draft.uploadImagesLoading = false;
        draft.uploadImagesError = action.error;
        break;
      case ADD_POST_REQUEST:
        draft.addPostLoading = true;
        draft.addPostDone = false;
        draft.addPostError = null;
        break;
      case ADD_POST_SUCCESS:        
        draft.addPostLoading = false;
        draft.addPostDone = true;
        draft.mainPosts.unshift(action.data);
        draft.imagePaths = [];
        break;
      case ADD_POST_FAILURE:
        draft.addPostLoading = false;
        draft.addPostError = action.error;
        break;
   
    }
  });
};

export default reducer;

saga

function uploadImagesAPI(data) {    
  console.log('사가의 데이터', data);
  return axios.post('/post/images', data);
}

function* uploadImages(action) {   
  try {        
    const result = yield call(uploadImagesAPI, action.data); 
    yield put({
      type: UPLOAD_IMAGES_SUCCESS,
      data: result.data,
    })
  } catch(err) {
    yield put({ 
      type: UPLOAD_IMAGES_FAILURE,
      data: err.response.data 
    })
  }  
}

function addPostAPI(data) {
  return axios.post('/post', data);
}

function* addPost(action) {     
  try {
    const result = yield call(addPostAPI, action.data);
    console.log(result.data);
    yield put({
      type: ADD_POST_SUCCESS,
      data: result.data,
    })
    yield put({
      type: BOARD_ADD_POST_TO_ME,
      data: result.data,
    })
  } catch(err) {
    yield put({ 
      type: ADD_POST_FAILURE,
      data: err.response.data 
    })
  }  
}

back/app.js

const express = require('express');
const cors = require('cors');
const passport = require('passport');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const dotenv = require('dotenv');
const morgan = require('morgan');
const path = require('path');

const postsRouter = require('./routes/posts');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const db = require('./models')
const app = express();
const passportConfig = require('./passport');

dotenv.config();
db.sequelize.sync()
  .then(() => {
    console.log('db 연결 성공');
  })
  .catch(console.error);

passportConfig();
app.use(morgan('dev'));

app.use(cors({
  origin: 'http://localhost:3060',
  credentials: true,
}));

app.use('/', express.static(path.join(__dirname, 'uploads')));
app.use(express.json({ limit: '100mb' }));
app.use(express.urlencoded({ limit: '100mb', extended: true }));

app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  saveUninitialized: false,
  resave: false,
  secret: process.env.COOKIE_SECRET,
}));
app.use(passport.initialize());
app.use(passport.session());

app.use('/posts', postsRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);

app.listen(3065, () => {
  console.log('서버 실행 중');
});

back/post.js

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const { Post, Comment, Image, User } = require('../models');
const { isLoggedIn } = require('./middlewares');

const router = express.Router();

try {
  fs.accessSync('uploads');
} catch (error) {
  console.log('uploads폴더가 존재하지 않아 생성합니다.');
  fs.mkdirSync('uploads');
}

const upload = multer({  
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads');      
    },
    filename(req, file, done) {
      const ext = path.extname(file.originalname);
      const basename = path.basename(file.originalname, ext);
      done(null, basename + '_' + new Date().getTime() + ext);      
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 },
});

// data: {
//   imagePaths,
//   content: value,
// }

router.post('/', isLoggedIn, async (req, res, next) => { // addPostAPI / POST /post
  try {
    const post = await Post.create({
      UserId: req.user.id,
      title: req.body.content.title,
      desc: req.body.content.desc,
      ingredient: req.body.content.ingredient,
      recipes: req.body.content.recipes,
      tips: req.body.content.tips,
      tags: req.body.content.tags,           
    });

    if (req.body.imagePaths) {
      if (Array.isArray(req.body.imagePaths)) {
        const images = await Promise.all(req.body.imagePaths.map((image) => Image.create({ src: image })));        
        await post.addImages(images);
      } else {
        const image = await Image.create({ src: req.body.imagePaths });
        await post.addImages(image);
      }
    };

    const fullPost = await Post.findOne({
      where: { id: post.id },
      include: [{
        model: Image,
      }, {
        model: Comment,
        include: [{
          model: User,
          attributes: ['id', 'nickname'],
        }],
      }, {
        model: User,
        attributes: ['id', 'nickname'],
      }, {
        model: User,
        as: 'Likers',
        attributes: ['id'],
      }]
    });

    console.log(fullPost);
    res.status(201).json(fullPost);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => {
  try {
    console.log('라우터', req.files);
    res.json(req.files.map((v) => v.filename));
  } catch (error) {
    console.error(error);
    next(error);
  }
});



module.exports = router;

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

서버 주소에 포트 앞에 :가 빠졌습니다.

그리고 저 에러는 Images[0]이 undefined인 것인데 게시글을 불러오기 전에 컴포넌트가 로딩된 것 같습니다. post.Images[0]?.src 이런 식으로 해결 가능합니다

hib4888님의 프로필 이미지
hib4888

작성한 질문수

질문하기