Hey guys I have been coding a little over a year now and focused almost entirely on front end (js, BUNCHA frameworks, CSS, html, etc etc)
it wasn't until literally yesterday that I came across the infamous CORS issue with spotify API.
while i have finished a udemy course that taught me a good deal of node.js/express i really didn't understand how to combine it all with front-end (react) until the other day. i started practicing
fetching data from the pokemon API before going to spotify simply because it is much easier to do (lol)... but i also know i can use express to enable cors etc. for the spotify app. ALL THAT ASIDE my question is essentially about parsing data into json using the simple .json() method.
how come in my backend (where i fetch the results) i have to use data.json() and then AGAIN i have to do it all? how come i also have to reimplement the async/await stuff in both iterations when it should all be done in the initial fetchdata function?
here is my fetchData.js file ---- where i initially call to the API
import fetch from "node-fetch"
const fetchData=async()=>{
const data=await fetch('https://pokeapi.co/api/v2/ability')
const res=await data.json()
const final=res.results
return res.results
}
export default fetchData
here is my server.js file where i call that function and send the data over when i client requests something from the path '/api/data'
import express from 'express';
const app=express()
import axios from 'axios'
import cors from 'cors'
import fetchData from './fetchData.js'
app.use(cors())
app.get('/api/data', async (req, res)=>{
const data=await fetchData()
res.send(data)
})
app.listen(3000, ()=>{
console.log('listening on port 3000')
})
and lastly here is my react component which makes a call to that path using the retrieval() function... how come i have to keep repeating async await logic AND .json() calls when the data seems to already be parsed and ready to go in the fetchData() function?
import React, {useState, useEffect} from 'react'
const Customers = () => {
const [pokemon, setPokemon]=useState([])
useEffect(()=>{
const retrieval=async()=>{
const data=await fetch('api/data')
const res=await data.json()
setPokemon(res)
}
retrieval()
console.log(pokemon)
},[])
return (
<div>
<ul>{pokemon.map(poke=>(
<li>{poke.name}</li>
))}</ul>
</div>
)
}
i guess if someone could just walk me through the whole process in layman's terms. im pretty decent with front-end, ui, and react etc.. but connecting backend is totally new to me and tbh the json.stringify(), json.parse(), and .json() stuff always confuses me... i usually just guess until it works haha. thanks in advance...
The reason you need to call .json() each time, is because you are actually making two API calls.
The first call is in the fetchData() function, where you are calling the Pokemon API.
The second call is in your retrieval() function, where you are actually calling your own API.
This is standard industry practice, and is always recommended to make calls to external APIs by way of a custom API endpoint from the frontend, instead of directly calling the external API from the frontend.
As a side note, you could easily utilize axios, which you seem to already have imported, to make the code a bit cleaner.
fetchData.js
import axios from 'axios';
const fetchData = async () => {
const response = await axios.get('https://pokeapi.co/api/v2/ability');
return response.data;
};
export default fetchData;
server.js
import express from 'express';
const app = express();
import cors from 'cors';
import fetchData from './fetchData.js';
app.use(cors());
app.get('/api/data', async (req, res) => {
const data = await fetchData();
return res.send(data);
});
app.listen(3000, () => {
console.log('listening on port 3000');
});
page.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Customers = () => {
const [pokemon, setPokemon] = useState([]);
useEffect(() => {
const retrieval = async () => {
const response = await axios.get('http://localhost:3000/api/data');
setPokemon(response.data);
};
retrieval();
console.log(pokemon);
}, []);
return (
<div>
<ul>{pokemon.map(poke => (
<li>{poke.name}</li>
))}</ul>
</div>
);
};
Related
Backend code example: I am trying to get users here from my SQL Server database Account:
async function executeQuery() {
try {
const pool = await sql.connect(config);
const result = await pool
.request()
.query(`USE Account SELECT TOP 10 UserNo FROM UserTable`);
return result;
} catch (err) {
console.log(err);
}
}
app.get("/api/data", async (req, res) => {
const result = await executeQuery();
res.json(result);
});
React frontend code: I am getting an error when try to render data from SQL Server.
import React, { useState, useEffect } from "react";
function SqlTest() {
const [data, setData] = useState([]);
async function fetchData() {
const result = await fetch("/api/data").then((res) => res.json());
setData(result);
}
useEffect(() => {
fetchData();
}, []);
return (
<div>
{data.map((item) => (
<div key={item.UserNo}>{item.UserNo}</div>
))}
</div>
);
}
export default SqlTest;
I am trying to render data from SQL Server, but nothing helps..
Ok, your problem is a very common one that has a very common solution.
You have 2 separate projects (HTTP servers):
The HTTP server that CRA comes with, which is a NodeJS server.
Your API server, which happens to also be a NodeJS server.
Now you want to fetch data from React by querying your API server.
Look at your URL: api/data. This is a relative URL. Relative URL's are resolved by the browser by using the current page's domain. I don't know your exact setup, but the URL will end up being something like http://localhost:3000/api/data.
Do you see the problem already? Your API server is not listening on port 3000. It is probably listening on some other port number. After all, no 2 applications can listen on the same TCP port.
So you would then be tempted to change your fetch URL to a full URL that specifies the server API: http://localhost:3001/api/data. That probably works, but there's a chance it errors out due to CORS.
So long story short, do as the CRA help pages say you should do: Set a proxy up in your CRA server. Here it is: https://create-react-app.dev/docs/proxying-api-requests-in-development/
In a nutshell, create the file src/setupProxy.js with code similar to this:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000', // <-- USE YOUR API PORT
changeOrigin: true,
})
);
};
The sample assumes you're running your API server in port 5000. Change it to fit your configuration.
I am trying to get function result from backend to frontend via axios but it returns 404 every time.
I managed to send request to backend and activate function but on geting result it started returning 404
route in app.ts
import cardRoute from './routes/test';
const app = express();
app.use('/test', cardRoute);
./routes/test.ts (backend)
function test_load() returns string
import express from 'express';
import { test_load } from '../cardpull';
const router = express.Router();
router.post('./test-bed',
async (req, res) => {
let cards = test_load()
res.send(cards);
},
);
export default router;
Frontend call
async function GetCard() {
var cards = await axios.post<string>('/test/test-bed');
return cards;
};
your route is not valid
router.post('./test-bed',
async (req, res) => {
let cards = test_load()
res.send(cards);
},
);
should be:
router.post('/test-bed',
async (req, res) => {
let cards = test_load()
res.send(cards);
},
);
and on your axios URL, maybe you need to include the host and port because if you define only the endpoint, it will hit your frontend host and port,
example if you open express on localhost:3000
then the axios will be
axios.post('http://localhost:3000/test/test-bed')
note: I didn't write the answer with typescript, but with javascript style should be clear enough.
I'm studying how to create some tests using the Jest with Nodejs, i'm actually using typescript.
When I try to run a simple test, by checking the status of the response, it shows the following error:
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
What could I do?
Here's my following codes:
session.test.ts =>
const request = require('supertest');
import app from '../../src/server';
describe('Authentication',() => {
it('should authenticate with valid credentials',async() =>{
const response = await request(app)
.post('/sessions')
.send({
email: "myemail#gmail.com",
password: "123456"
})
await expect(response.status).toBe(200);
});
});
SessionController.ts =>
import {Request, Response} from 'express';
export default class SessionController{
async store(request: Request, response: Response){
return response.status(200);
}
}
server.ts =>
import express from 'express';
import routes from './routes';
require("dotenv").config({
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env"
});
const app = express();
app.use(express.json());
app.use(routes);
app.listen(3333);
export default app;
and routes.ts:
import express from 'express';
import UsersController from './controllers/UsersController';
import SessionController from './controllers/SessionController';
const routes = express.Router();
const usersControllers = new UsersController();
const sessionController = new SessionController();
routes.post('/users',usersControllers.create);
routes.post('/sessions',sessionController.store);
export default routes;
at my SessionController.ts, I had to put the following:
import {Request, Response} from 'express';
export default class SessionController{
async store(request: Request, response: Response){
return response.status(200).send()
}
}
I forgot to send haha
The first thing is to check if there are some error in the request, or (more likely) if it remain in a pending state, because 5 seconds are tons of time.
Anyway you can specify the test timeout like follow
describe('Authentication',() => {
it('foobar', async function () { // no arrow function
this.timeout(10000)
await myFunc()
});
});
I am not sure you are actually 'completing' the request using the supertest API.
The fluent chaining approach of supertest allows you to carry on adding 'steps' to the HTTP request before actually dispatching it. Unfortunately, send() is preparing a send step for when you dispatch. It doesn't actually dispatch as you can see from this superagent example in which many further configuration steps follow send() and it's only end() which runs them all.
In several supertest examples I saw there is a chained 'expect' call which would presumably also trigger the actual HTTP post.
Equally, the 'end()' docs at https://github.com/visionmedia/supertest#endfn say...
Perform the request and invoke fn(err, res)
This indicates to me that until you send a 'finalising' call, there won't be a request.
I try to get the :userId "albert" from this url
http://localhost:5000/search/albert?query=al&page=1
at server side but failed, what can I do to get the react-router defined params correctly at node.js with express?
routes.js
[
{
path: '/search/:userId',
component: Search,
}, {
path: '/search',
component: Search,
}
...
]
server.js
server.get('*', async (req, res, next) => {
const pageData = await routes
.filter(route => matchPath(req.path, route))
.map((route) => {
console.log(route)
return route.component
})
}
The React-Router Way
React Router V4 does include a way to extract param data server-side using their matchPath() function, using their standard parameter implementation, "/path-name/:param" route matching.
In this case, it allows me to do a lot of server-side stuff based on the parameter before the express app responds with the page data.
NOTE: this is probably not the most basic implementation, but it's a pared down version of my complete SSR react implementation that makes use of matchPath().
Requirements
Server-side rendered react app
React-router-dom v4
Centralized routes file (because SSR)
Express app server (I'm hosting my express app on Firebase)
In This Example, a server-side express app attempts to run an "initialAction" function in each component during a fresh page load. It passes promise resolve and reject to know when the function is completed running, and the request object which may contain useful params we can extract with matchPath(). It does this for every matching route, again, using matchPath().
Routes.js Example
Where :id is the "id" param in the URL.
const routes = [
{
path: "/news-feed/:id",
component: NewsFeed,
exact: true
},
]
export default routes;
Component Example
Just showing the initialAction() function in the component
import { Link, matchPath } from 'react-router-dom';
class NewsFeed extends Component {
// Server always passes ability to resolve, reject in the initial action
// for async data requirements. req object always passed from express to
// the initial action.
static initialAction(resolve, reject, req) {
function getRouteData() {
let matchingRoute = routes.find(route => {
return matchPath(req.path, route);
});
console.log("Matching Route: ", matchingRoute);
return matchPath(req.path, matchingRoute);
}
let routeData = getRouteData();
console.log("Route Data: ", routeData);
}
/** REST OF COMPONENT **/
Console.log output for the url www.example.com/news-feed/test would be
Route Data: { path: '/news-feed/:id',
url: '/news-feed/test',
isExact: true,
params: { id: 'test' } }
As you can see, we've found our param on the server-side using no regex. matchPath() did the work for us. We can use nice, clean urls.
Server-side index.js
Where the initial action is called, with the promise resolve, reject, and req objects. Keep in mind this is a firebase hosting example and may differ for different hosting providers - your method for the initialAction function call may also differ.
import React from "react";
import ReactDOMServer from 'react-dom/server';
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { StaticRouter, matchPath } from "react-router-dom";
import routes from "../shared/components/App/routes.js";
import express from "express";
import * as functions from "firebase-functions";
// Import Components, Reducers, Styles
import App from "../shared/components/App";
import reducers from "../shared/reducers";
// Prepare our store to be enhanced with middleware
const middleware = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
// Create store, compatible with REDUX_DEVTOOLS (chrome extension)
const store = createStoreWithMiddleware(reducers);
// Implement cors middleware to allow cross-origin
const cors = require('cors')({ origin: true });
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Finds the component for the given route, runs the "initial action" on the component
// The initialAction is a function on all server-side renderable components that must retrieve data before sending the http response
// Initial action always requires (resolve, reject, req), and returns a promise.
const promises = routes.reduce((acc, route) => {
if (matchPath(req.url, route) && route.component && route.component.initialAction) {
acc.push(new Promise(function (resolve, reject) {
// console.log("Calling initial action...");
store.dispatch(route.component.initialAction(resolve, reject, req));
}));
}
return acc;
}, []);
// Send our response only once all promises (from all components included in the route) have resolved
Promise.all(promises)
.then(() => {
const context = {};
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
);
const preloadedState = store.getState();
res.status(200).send(renderFullPage(html, preloadedState));
})
.catch(function (error) {
console.log("Promise error at server", error);
});
});
});
module.exports = functions.https.onRequest(app);
Just used a sample node.js app to make a server.js which could be like
const express = require('express')
const app = express()
app.get('/search/:userid', (req, res) => res.json({ key: `Hello World for search with id=${req.params.userid}` }))
app.get('/search', (req, res) => res.send('Hello World!i for search'))
app.get('*', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
For the page number and other url params you can do like
req.query['page']
to retrieve the parameters.
Am pretty new to JS, but I am trying my best to learn. As it is, I am trying to mock express. Here is my base class (cut down, for testing purposes):
import compression from 'compression';
import express from 'express';
export default class Index{
constructor(){}
spawnServer(){
console.log(express());
let app = express();
app.use(STATIC_PATH, express.static('dist'));
app.use(STATIC_PATH, express.static('public'));
etc...
}
}
And here is the test I am trying to achieve, in a separate test file...:
test('should invoke express once', () =>{
index.spawnServer();
expect(mockExpressFuncs().use.mock.calls.length).toBe(3);
})
My question is - how do I make the test override the require of the class under test - is that even possible? I want to have my Index use a mocked version of express, one that includes express() and express.require.
I did read through the documentation, and attempted something like:
const mockFunction = function() {
return {
use: useFn,
listen: jest.fn()
};
};
beforeEach(() => {
jest.mock('express', () => {
return mockFunction;
})
express = require('express');
});
But that did not work - what am I doing wrong? :(
Thanks.
Create the mock app object and make it be returned by express module.
Then you can check how many times app.use have been called using either expect(app.use.mock.calls.length).toBe(3) or better expect(app.use).toHaveBeenCalledTimes(1)
const app = {
use: jest.fn(),
listen: jest.fn()
}
jest.doMock('express', () => {
return () => {
return app
}
})
test('should invoke express once', () => {
expect(app.use).toHaveBeenCalledTimes(1)
})