묻고 답해요
144만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
수정하기 기능
이제 promise.all을 사용해서 이미지 미리보기는 가짜 URL로 보여주고게시글 등록 mutation을 날릴때 이미지 upload 요청을 보내고 응답받은 url을 mutation을 날리고 있는데수정하기할때도 uploadImage을 하기 위해서 코드의 양이 길어지는데 코드는 길더라도 이게 더 효율적인 방식인건가요?? 그리고 이렇게하면 fetchBoards의 결과로 받아온 결과는 string 타입이고 uploadImage에 사용하는 이미지는 FIle 타입이라서 creacteBoard처럼 각각 다른 배열에 담아서 보내기는 어렵고 만약 upload하고 응답받은 url이면 skip하고 File타입의 데이터라면 imageUpload를 실행시켜 url을 받아오와 배열에 추가해서 updateBoardInput 객체에 추가해서 mutation 보내려고 하는데 이런식으로 접근하는게 맞는지 궁금합니다
-
해결됨대용랑 채팅 TPS에 대한 stateful 서비스 구축하기
컨트롤 서버가 다운되면 어떻게 되나요?
[이 강의에서 다룰 zero-downtime-deploy를 위한 아키텍처 구조도를 알려드릴게요] 강의를 들으면서 컨트롤 서버의 필요성에 대해 설명해주셨습니다. 채팅 서버 1,2,3 이 다운될 때를 고려해서 로드 밸런싱 용으로 컨트롤 서버를 따로 둔다고 이해했는데, 컨트롤 서버가 배포되거나 다운되는 경우는 없을까요? 컨트롤 서버도 이중화로 구성을 해야하는 것인지, 다른 방법은 무엇이 있는지 궁금합니당
-
해결됨차세대 Node.js 백엔드 서버 개발(Fastify & Prisma & Typescript와 함께하는)
섹션 5 login 인증 기능
섹션5 인증관련 login 기능 코드를 수업과 똑같이 작성 했는데 postman 에서{ "success": false, "status": 400, "message": "Bad Request" } 에러 뜹니다.터미널에서 뜨는내용{"level":50,"time":1718325166296,"pid":23544,"hostname":"DESKTOP-BDGKO8E","reqId":"req-d","err":{"type":"Error","message":"\"expiresIn\" should be a number of seconds or string representing a timespan eg: \"1d\", \"20h\", 60","stack":"Error: \"expiresIn\" should be a number of seconds or string representing a timespan eg: \"1d\", \"20h\", 60\n at module.exports [as sign] (D:\\Practice\\slog-fastify-prisma-ts-study\\node_modules\\jsonwebtoken\\sign.js:213:22)\n at generateRefreshToken (d:\\Practice\\slog-fastify-prisma-ts-study\\src\\lib\\authHelper.ts:54:30)\n at Object.loginWithPassword (d:\\Practice\\slog-fastify-prisma-ts-study\\src\\services\\authService.ts:46:30)\n at Object.<anonymous> (d:\\Practice\\slog-fastify-prisma-ts-study\\src\\routes\\auth\\index.ts:24:28)"},"msg":"\"expiresIn\" should be a number of seconds or string representing a timespan eg: \"1d\", \"20h\", 60"}{"level":30,"time":1718325166297,"pid":23544,"hostname":"DESKTOP-BDGKO8E","reqId":"req-d","res":{"statusCode":400},"responseTime":44.27779999934137,"msg":"request completed"}어디 부분을 수정해야 하는지요?
-
해결됨[2024] 한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
동적경로 사용 오류
안녕하세요. Route 부분에 오류가 생겼는데 어떤 부분이 잘못되었는지 모르겠어서 질문 남깁니다.Route 함수에 path 경로를 설정할 때, 동적경로를 설정하고 싶으면 ":"과 함께 파라미터의 값을 적어주는 것으로 압니다. 강사님께서 적은 코드와 제 코드가 다른 점이 없는데, 저는 해당 url에 접근했을 때 url경로에 ':'이 포함되어서 나옵니다. 구글링+gpt를 사용해서 해당 오류를 고치려고 해보았지만 찾을 수 없는 상태입니다. 어떤 부분이 문제일까요? import "./App.css"; import { useReducer, useRef, createContext } from "react"; import { Routes, Route } from "react-router-dom"; import Home from "./Pages/Home"; import New from "./Pages/New"; import Diary from "./Pages/Diary"; import Edit from "./Pages/Edit"; import Notfound from "./Pages/Notfound"; function reducer(state, action) { switch (action.type) { case "CREATE": return [action.data, ...state]; case "UPDATE": return state.map((item) => String(item.id) === String(action.data.id) ? action.data : item ); case "DELETE": return state.filter((item) => String(item.id) !== String(action.data.id)); default: return state; } } const mockData = [ { id: 1, createdData: new Date("2024-06-12").getTime(), emotionId: 1, content: "1번 일기 내용", }, { id: 2, createdData: new Date("2024-06-11").getTime(), emotionId: 2, content: "2번 일기 내용", }, { id: 3, createdData: new Date("2024-05-11").getTime(), emotionId: 3, content: "3번 일기 내용", }, ]; export const DiaryStateContext = createContext(); export const DiaryDispatchContext = createContext(); function App() { const [data, dispatch] = useReducer(reducer, mockData); const idRef = useRef(3); // 새로운 일기 추가 const Create = (createdData, emotionId, content) => { dispatch({ type: "CREATE", data: { id: idRef.current++, createdData, emotionId, content, }, }); }; // 기존 일기 수정 const Update = (id, createdData, emotionId, content) => { dispatch({ type: "UPDATE", data: { id, createdData, emotionId, content, }, }); }; // 기존 일기 삭제 const Delete = (id) => { dispatch({ type: "DELETE", data: { id, }, }); }; return ( <> <DiaryStateContext.Provider value={data}> <DiaryDispatchContext.Provider value={{ Create, Update, Delete }}> <Routes> <Route path="/" element={<Home />} /> <Route path="/new" element={<New />} /> <Route path="/diary/:id" element={<Diary />} /> <Route path="/edit/:id" element={<Edit />} /> <Route path="*" element={<Notfound />} /> </Routes> </DiaryDispatchContext.Provider> </DiaryStateContext.Provider> </> ); } export default App;
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
이모션에서 props전달시 화살표함수가 원래 이렇게 생겼나요?
다른 부분에서 화살표함수 만들면 제대로 만들어지는데 이부분에서만 화살표함수 모양이 다릅니다.
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
07-01 emotion에서 if문을 다 넣어도 노란색으로 변하질 않습니다.
onChangeContents 함수 부분에 넣었을때 처음엔 버튼이 노란색으로 변했습니다만 이후 onChangeWriter 와 onChangeTItle 부분까지 넣은 후 버튼의 색이 변하지 않습니다.if문을 다 지우고 setIsActive(true)만 넣었을때는 작동이 됩니다..
-
해결됨[2024] 한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
투두 추가할 때 onChangeContent 작성 이유
8.4) Create - 투두 추가하기 강의를 들으며 4분대 경 코드를 작성 중입니다. import "./Editor.css"; import { useState } from "react"; const Editor = ({ onCreate }) => { const [content, setContent] = useState(""); const onChangeContent = (e) => { setContent(e.target.value); }; const onSubmit = () => { onCreate(); }; return ( <div className="Editor"> <input value={content} onChange={onChangeContent} placeholder="새로운 Todo..." /> <button onClick={onSubmit}>추가</button> </div> ); }; export default Editor;이 부분에서, input에 들어가는 content는 추가 버튼을 클릭할 때만 value를 setContent로 해줘도 될 것 같은데요, 왜 onChangeContent로 값이 바뀔 때마다 밸류를 저장해주는지 궁금합니다. 타이핑할때마다(값이 바뀔 때마다) 저장할 필요없이 마지막에 추가할 때만 저장되어도 되지 않나 싶어서 궁금해졌습니다.
-
미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
가드의 장점에 대해서 질문이 있습니다.
안녕하세요. 유저가 가진 권한에 따른 접근 제어 개발을 하다 문득 궁금한 것이 생겼는데요.유저가 권한을 갖고있는지 확인하고, 접근을 허용/차단 하는 코드를 서비스에서 작성 할 수도 있고, 커스텀 데코레이터를 작성해서 컨트롤러 단에서 막을 수도 있잖아요. 둘 중 어떤 방법이 효율적인 방법인지, 장/단점은 무엇인지가 궁금해졌습니다.곰곰이 생각해봐도 차이가 있는 장/단점을 모르겠네요... 어떤 방법이 더 좋은 방법일까요?
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
리액트와 next.js 강의에 대해 여쭤볼게 있습니다.
안녕하세요 우선 강의 열심히 잘 보고 있습니다. 다른게 아니라 리액트와 next.js 수업이 조금 헷갈리는데 섹션5부분만 리액트이고 그 뒤에는 next.js라고 보면 되나요? 강의를 보다가 어느순간 제가 알고있는 리액트의 폴더구조들이 조금 다르더라구요. 리액트에서는 폴더중에 App.js인데 _app.js 이런식으로 되어있고 또 마지막 포트폴리오 리뷰에서는 화면에 리액트 로고부분도 나와있어서 어디서부터 어디까지가 리액트이고 next.js 강의인지 알려주시면 너무 감사드리겠습니다 ^^ 마지막으로 포트폴리오 리뷰는 제가 어떠한걸 보고 만든 후 보는 영상인지 아니면 그냥 영상을 보고 같이 하는건지도 알고싶습니다.
-
미해결[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
refreshToken 적용 이후
포트폴리오를 만드는중에 refreshToken을 이용해서 로그인 데이터를 저장한 후에 생긴 문제입니다.게시글 조회 페이지에서 새로고침을 하면 fetchBoards와 fetchBoardCount 요청이 2번씩 나가고뒤에오는 요청은 canceled 되는데 이런 경우는 어떤거 때문에 생기는건지 궁금합니다.localStorage로 되돌리면 다시 오류없이 잘 동작합니다refreshToken을 사용하고 저 오류가 생기면 게시글 등록후에 간헐적으로 새로고침을 해야 refetch되는거 같습니다.
-
미해결코로나맵 개발자가 알려주는 React + Express로 지도서비스 만들기 (Typescript)
MongoDB Compass 관련 질문
MongoDB compass에서 new connection에서 말씀하신 대로 mern, merntest, admin 이렇게 설정해주고, connect 버튼을 눌렀는데 Authentication failed라는 오류가 뜹니다. 어느 부분에서 오류가 난 건가요..?
-
해결됨[2024] 한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
local에서는 이상없이 되는데 vercel 배포 이후에 아무것도 없을 때가 보입니다.
현재 vercel에 정상 배포한 후 확인해보았는데 src/pages/Diary.jsx 에 존재하는 if (!curDiaryItem) { return <div>데이터 로딩중...</div>; } 이렇게 설정한 부분만 보이고 기존에 보여져야 할 것들이 모두 안보이는데 이유를 모르겠습니다 ㅠ 그리고 <meta property="op:image" content="/thumbnail.png" /> 로 지정해주었는데 왜 이것만 잘 안되는지 모르겠습니다.. 현재 vercel 주소입니다.https://emotion-diary-sable-theta.vercel.app/github repo주소입니다.https://github.com/jjacksang/one-bite-react-v2
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
webpack 관련 에러 질문
백엔드 run은 가능하지만, front에서 run이 안되더라구요. 원인은 webpack 관련 에러인 것을 확인했고, chat gpt를 통해 에러 해결하려 하니, 또 다른 에러가 발생했습니다. 제가 잘못 건들인건지 잘 모르겠지만, 왜 그런지 알고 싶습니다. 혹시나 해서 깃도 같이 올려드립니다. https://github.com/bukwon/boiler-plate
-
미해결비전공자를 위한 진짜 입문 올인원 개발 부트캠프
노드 설치가 안됩니다
우선 노드 설치페이지가 기존과 매우 크게 달라져서 해당 부분 업데이트를 해주시거나 피드백을 주셔야 할 것 같습니다.그래도 우선 사이트에 들어가서 설치를 시도해 보았는데요이와 같은 화면이 떠서 우선 여기에 제공된 코드를 복사하여 cmd에서 실행을 하였으나 설치중 아래와 같은 에러가 발생하였습니다.강의제공자께서 보여주신 설치화면과 매우 달라서 어떻게 접근해야할 지 부터 어려웠고, 시도를 했음에도 에러가 이렇게 생기는데 어떻게 해야 제대로 노드가 설치가 될까요? 일단 설치가 되어야 다음 단계로 넘어가든 할 텐데 해결이 안되고 있습니다...
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
next, node 버전 / 폴더 구조 질문 드립니다.
1번 질문next9 버전을 다운받아서 실행 했더니 오류가 나서 찾아봤는데 node버전이 높아서 호환 관련 문제로 실행이 안되는 오류가 있었습니다. next14 버전으로 진행했는데 앞으로도 괜찮나요? 2번 질문pages 폴더안에 index.js파일은 /이고 그 외에 이름은 /profile, /signup 구조인것 같은데 어떤 경우에 폴더구조를 사용하고 어떤 경우에 js파일의 이름을 변경해서 사용해야 하는지 궁금합니다.
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
리액트 관련 질문
react를 실행하려는데 해당 에러가 발생했습니다. 어떻게 해결해야 할까요? 검색해도 나와있지 않습니다
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
url 오류 질문있습니다
프론트 axios에서 baseurl을 https://api.count101.shop으로 설정했는데 요청을 보내보니깐 request url이 https://count101.shop/https/api.count101.shop/user/autoLogin 이런식으로 앞에 https://count101.shop이 붙어버리는데 어디를 수정해야 될 지 모르겠습니다,,
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
상품등록 기능
상품 등록시에 판매자 데이터를 넣는거같은데 gpl query문에서 seller, buyer를 어떤 형식으로 작성해야 되는지 모르겠습니다데이터는 fetchUserLoggedin으로 넣는게 맞는지도 궁금합니다
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
실시간 채팅방에서 GIF uploads 하면 GIF가 바로 화면에 보이지 않고 새로 고침을 해야 보이는데 어떻게 해야 할까요?
실시간 채팅방 강좌 코드를 작성하여 작동 시켜 본 결과 메시지 전송 까지는 잘 되는 것을 확인 하였는데 GIF 업로드 시 다음 그림과 같은 현상이 발생하고 있습니다그림 하단에 표시한 부분 처럼 처음에 GIF 올리기를 하면 그림이 보이지 않다가 새로 고침을 하면 위의 다른 GIF 처럼 잘 보이긴 하는데 무슨 문제 일까요?참고로 관련 코드를 같이 올립니다chat.html {% extends 'layout.html' %} {% block content %} <h1>{{title}}</h1> <a href="/" id="exit-btn">방 나가기</a> <fieldset> <legend>채팅 내용</legend> <div id="chat-list"> {% for chat in chats %} {% if chat.user === user %} <div class="mine" style="color: {{chat.user}}"> <div>{{chat.user}}</div> {% if chat.gif %}} <img src="/gif/{{chat.gif}}"> {% else %} <div>{{chat.chat}}</div> {% endif %} </div> {% elif chat.user === 'system' %} <div class="system"> <div>{{chat.chat}}</div> </div> {% else %} <div class="other" style="color: {{chat.user}}"> <div>{{chat.user}}</div> {% if chat.gif %} <img src="/gif/{{chat.gif}}"> {% else %} <div>{{chat.chat}}</div> {% endif %} </div> {% endif %} {% endfor %} </div> </fieldset> <form action="/chat" id="chat-form" method="post" enctype="multipart/form-data"> <label for="gif">GIF 올리기</label> <input type="file" id="gif" name="gif" accept="image/gif"> <input type="text" id="chat" name="chat"> <button type="submit">전송</button> </form> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> const socket = io.connect('http://localhost:8005/chat', { path: '/socket.io', }); socket.emit('join', new URL(location).pathname.split('/').at(-1)); socket.on('join', function (data) { const div = document.createElement('div'); div.classList.add('system'); const chat = document.createElement('div'); div.textContent = data.chat; div.appendChild(chat); document.querySelector('#chat-list').appendChild(div); }); socket.on('exit', function (data) { const div = document.createElement('div'); div.classList.add('system'); const chat = document.createElement('div'); div.textContent = data.chat; div.appendChild(chat); document.querySelector('#chat-list').appendChild(div); }); socket.on('chat', function (data) { const div = document.createElement('div'); if (data.user === '{{user}}') { div.classList.add('mine'); } else { div.classList.add('other'); } const name = document.createElement('div'); name.textContent = data.user; div.appendChild(name); if (data.chat){ const chat = document.createElement('div'); chat.textContent = data.chat; div.appendChild(chat); } else { const gif = document.createElement('img'); gif.sr = '/gif/' + data.gif; div.appendChild(gif); } div.style.color = data.user; document.querySelector('#chat-list').appendChild(div); }); document.querySelector('#chat-form').addEventListener('submit', function (e) { e.preventDefault(); if (e.target.chat.value) { axios.post('/room/{{room._id}}/chat', { chat: this.chat.value, }) .then( () => { e.target.chat.value = ''; }) .catch( (err) => { console.error(err); }); } }); document.querySelector('#gif').addEventListener('change', function (e) { console.log('******',e.target.files); const formData = new FormData(); formData.append('gif', e.target.files[0]); axios.post('/room/{{room._id}}/gif', formData) .then( () => { e.target.file = null; }) .catch( (err) => { console.error(err); }); }); </script> {% endblock %} routes/index.jsconst express = require('express'); const { renderMain, renderRoom, createRoom, enterRoom, removeRoom, sendChat, sendGif } = require('../controllers'); const multer = require('multer'); const fs = require('fs'); const path = require('path'); const router = express.Router(); router.get('/', renderMain); router.get('/room', renderRoom); router.post('/room', createRoom); router.get('/room/:id', enterRoom); router.delete('/room/:id', removeRoom); router.post('/room/:id/chat', sendChat); try {fs.readdirSync('uploads'); } catch (err) { console.error('uploads 폴더가 없어 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); done(null, path.basename(file.originalname, ext) + Date.now() + ext); }, }), limits: {fileSize: 5 * 1024 *1024 }, }) router.post('/room/:id/gif', upload.single('gif'), sendGif); module.exports = router;controllers/index.jsconst Room = require('../schemas/room'); const Chat = require('../schemas/chat'); const { removeRoom: removeRoomService } = require('../services'); exports.renderMain = async ( req, res, next ) => { try{ const rooms = await Room.find({}); res.render('main', {rooms, title: 'GIF 채팅방'}); } catch (error) { console.error(error); next(error); } }; exports.renderRoom = ( req, res, next ) => { res.render('room', { title: 'GIF 채팅방 생성'}); }; exports.createRoom = async ( req, res, next ) => { try{ const newRoom = await Room.create({ title: req.body.title, max: req.body.max, owner: req.session.color, password: req.body.password, //session data 에서 옮 }); const io = req.app.get('io'); io.of('/room').emit('newRoom', newRoom); // 방에 들어가는 부분 if (req.body.password ) { res.redirect(`/room/${newRoom._id}?password=${req.body.password}`); } else { res.redirect(`/room/${newRoom._id}`); } } catch (error) { console.error(error); next(error); } }; exports.enterRoom = async( req, res, next ) => { try{ const room = await Room.findOne({_id: req.params.id}); if (!room){ return res.redirect('/?error=존재하지 않는 방입니다.'); } if (room.password && room.password !== req.query.password ){ return res.redirect('/?error=비밀번호가 틀렸습니다.'); } const io = req.app.get('io'); const { rooms } = io.of('/chat').adapter; if (room.max <= rooms.get(req.params.id)?.size) { return res.redirect('/?error=허용 인원을 초과하였습니다.'); } const chats = await Chat.find({room: room._id }).sort('createdAt'); res.render('chat', { title: 'GIF 채팅방 생성', chats , room, user: req.session.color }); } catch (error) { console.error(error); next(error); } }; exports.removeRoom = async ( req, res, next ) => { try { await removeRoomService(req.params.id ); res.send('ok'); setTimeout(() => { req.app.get('io').of('/room').emit('removeRoom', req.params.id); }, 2000) } catch (error) { console.error(error); next(error); } }; exports.sendChat = async (req, res, next ) =>{ try { const chat = await Chat.create({ room: req.params.id, user: req.session.color, chat: req.body.chat, }); req.app.get('io').of('/chat').to(req.params.id).emit('chat', chat); res.send('ok'); } catch( error ){ console.error(error); next(error); } } exports.sendGif = async (req, res, next ) => { try { const chat = await Chat.create({ room : req.params.id, user: req.session.color, gif: req.file.filename, }) setTimeout(() => { req.app.get('io').of('/chat').to(req.params.id).emit('chat',chat); }, 1000); res.send('ok'); } catch (error) { console.error(error); next(error); } } [제로초 강좌 질문 필독 사항입니다]질문에는 여러분에게 도움이 되는 질문과 도움이 되지 않는 질문이 있습니다.도움이 되는 질문을 하는 방법을 알려드립니다.https://www.youtube.com/watch?v=PUKOWrOuC0c0. 숫자 0부터 시작한 이유는 1보다 더 중요한 것이기 때문입니다. 에러가 났을 때 해결을 하는 게 중요한 게 아닙니다. 왜 여러분은 해결을 못 하고 저는 해결을 하는지, 어디서 힌트를 얻은 것이고 어떻게 해결한 건지 그걸 알아가셔야 합니다. 그렇지 못한 질문은 무의미한 질문입니다.1. 에러 메시지를 올리기 전에 반드시 스스로 번역을 해야 합니다. 번역기 요즘 잘 되어 있습니다. 에러 메시지가 에러 해결 단서의 90%를 차지합니다. 한글로 번역만 해도 대부분 풀립니다. 그냥 에러메시지를 올리고(심지어 안 올리는 분도 있습니다. 저는 독심술사가 아닙니다) 해결해달라고 하시면 아무런 도움이 안 됩니다.2. 에러 메시지를 잘라서 올리지 않아야 합니다. 입문자일수록 에러메시지에서 어떤 부분이 가장 중요한 부분인지 모르실 겁니다. 그러니 통째로 올리셔야 합니다.3. 코드도 같이 올려주세요. 다만 코드 전체를 다 올리거나, 깃헙 주소만 띡 던지지는 마세요. 여러분이 "가장" 의심스럽다고 생각하는 코드를 올려주세요.4. 이 강좌를 바탕으로 여러분이 응용을 해보다가 막히는 부분, 여러 개의 선택지 중에서 조언이 필요한 부분, 제 경험이 궁금한 부분에 대한 질문은 대환영입니다. 다만 여러분의 회사 일은 질문하지 마세요.5. 강좌 하나 끝날 때마다 남의 질문들을 읽어보세요. 여러분이 곧 만나게 될 에러들입니다.6. 위에 적은 내용을 명심하지 않으시면 백날 강좌를 봐도(제 강좌가 아니더라도) 실력이 늘지 않고 그냥 코딩쇼 관람 및 한컴타자연습을 한 셈이 될 겁니다.
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 백엔드 코스
선생님 그러면 만약에 도커로 백엔드 실행하지말고 구냥..
만약 도커로 백엔드 실행하기싫고,내 로컬에서 백엔드 실행한다고하면 mongoose .connect("mongodb://my-database:27017/mydocker") .then(() => { console.log("DB 연결 성공"); }) .catch((err) => console.log("DB 연결 실패", err));connect부분을 로컬호스트:27017로만 변경하면 되는거는 알겠는데..너무 번거로운거같은데 혹시 .env에 설정하는 방식같은게 없을까요? 예를들면 프로덕션, 개발모드에 따라서env를 바꿀수있잖아유그런것처럼 도커컨테이너 환경 혹은 그냥 로컬에서 백엔드 실행했을때환경 이런걸 구분할수있는 방법은없나유?