I want to retrieve data that is at this link: https://api.rajaongkir.com/starter/cost using express.js.
I created the Single Page Application website using react.js for frontend so i need to call this route : /shipping/check/cost in my backend for get the data. but i dont know how to request inside router express.
I have never done a Restful API from someone else's website.
I just copied what was in the documentation, but in the documentation using the node.js not express.js. https://rajaongkir.com/dokumentasi/starter#cost-response
when I run this I get nothing.
My routes
import {Router} from 'express';
import * as ShippingRouter from './controller'
const routes = new Router();
routes.post('/shipping/check/cost',ShippingRouter.checkCost);
export default routes;
Controller
import db from '../../config/conn';
import keys from '../../config/keys';
import jwt from 'jsonwebtoken';
import qs from 'querystring';
import request from 'request';
export const checkCost =(req,res)=>{
var options = {
"method": "POST",
"hostname": "api.rajaongkir.com",
"port": null,
"path": "/starter/cost",
"headers": {
"key": "mykey",
"content-type": "application/x-www-form-urlencoded"
}
};
var reqCost= https.request(options,function(ress){
var chunks = [];
ress.on("data", function (chunk) {
chunks.push(chunk);
});
ress.on("end", function () {
var body = Buffer.concat(chunks);
res.json(body.toString());
});
})
reqCost.write(qs.stringify({
origin: '501',
destination: '114',
weight: 1700,
courier: 'jne'
}));
reqCost.end();
}
The first thing I would check is if the POST HTTP verb you are using is the correct one. You said you wanted to get data from the API, in this case, you should be using GET instead of POST (check the API documentation to know more).
Besides that, please check this repository where I'm using express and how I handle calls.
Related
I am wondering why the Fetch API in javascript isn't call the endpoint I created in express. When I start my server and go to the '/characters' endpoint, it returns correctly formatted JSON.
Here is my express index.js
const app = express();
const PORT = 3000;
const charactersRoute = require('./routes/characters');
//Characters Route
app.use('/characters', charactersRoute)
app.listen(PORT, function(err) {
if(err) console.log(err);
console.log(`Server is listening on port ${PORT}`)
})
Here is my Characters Route
const express = require('express'); // Web Framework
const https = require('https');
const router = express.Router();
const PORT = 3000;
// app.listen(PORT, function(err) {
// if(err) console.log(err);
// console.log(`Server is listening on port ${PORT}`)
// })
const api = 'https://www.breakingbadapi.com/api/characters/?limit=20';
router.get("/", function(req, res) {
https.get(api, (response) => {
console.log(response.statusCode);
response.on('data', (d) => {
const data = JSON.parse(d);
res.send(data);
})
// res.send("Running")
})
})
module.exports = router;
Here is my Characters.jsx file where I'm using Fetch
import React, {useEffect, useState} from 'react';
import Card from '#mui/material/Card';
import axios from 'axios';
export default function Character() {
const [data, setData] = useState();
useEffect(() => {
fetch('/characters')
.then(res => res.json())
.then(data => setData(data))
// console.log(data);
}, []);
}
When I run my front end and check the response I receive, it returns my index.html.
If I fetch the API URL itself, then it correctly returns the JSON in my frontend. But When I try to fetch the API endpoint I created in express, I don't get any data. Any suggestions?
You did not set the endpoint for the fetch function. It doesn't know what API '/characters' is. It is similar to saying the house number but not telling the street, you don't know where to go. So you need to pass the absolute path to fetch to request data from the server, because the server is a different 'entity', it's not the same with your front-end. Therefore, you need to provide the full API URL. Or, if using axios, since I see you imported it above, you must set axios.defaults.baseURL = <API_URL>, (or provide the full URL in the request itself just like in fetch) and then make the request with the help of axios, not fetch.
Therefore your React code will look a little something like this:
import React, {useEffect, useState} from 'react';
import Card from '#mui/material/Card';
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000';
export default function Character() {
const [data, setData] = useState();
useEffect(() => {
const getData = async () => {
try {
const {data} = await axios.get('/characters');
setData(data);
} catch (err) {
console.error(err);
}
};
getData();
}, []);
}
But I suggest you create a different file with all the axios requests and there you set the baseUrl - that's a better practice.
I think is that you are not using cors in your app.
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
Try this, I hope it helps you!
Error pattern
This kind of error occurs when we send a request from the client-side to the server-side using a relative URL (/characters in this case). But the frontend app and the backend app run on 2 different ports.
Reason
When we use relative URLs, the URL will be concatenated with the current host (the frontend host, not the backend). Usually, we receive a 404 error because the resource doesn't exist.
Example
Backend Express app running on port 5000. React app running on port 3000 for development. In React app, if we send a request to /users, then the full URL is http://localhost:3000/users. The request goes to the React app, not the backend server. And we don't receive the desired output.
Action
You should use an absolute URL, something like: http://localhost:5000/users to send the request to your backend app. Consider saving the host part (http://localhost:5000) in a global variable in order to use it in multiple places in your frontend code. When you have different environments for the backend (DEV/STAGING/PRODUCTION), you can change the backend host in only 1 place.
I am trying to access params in my Next.js API routes. I am just wondering why in order to access a certain dynamic route id I must use req.query when normally in express I would access the id via params. Is req.query the correct way of accessing the id?
I have the following api folder structure.
/pages
/api
/posts
-index.js
-[postId].js
In my [postId] file I have the following
function handler(req, res) {
// shows undefined
console.log(req.params);
const postId = req.params.postId;
and my api call is as follows:
fetch(`/api/posts/${id}`)
Referring to nextJs docs, all examples uses req.query to get the id or other parameters:
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
So there's nothing wrong with this approach.
Nextjs doesn't explicit says why they use query instead of params, but in the section about dynamic routes you can get some clues:
the route /post/abc?foo=bar will have the following query object:
{ "foo": "bar", "pid": "abc" }
Probably they use query as a way to join both params and query in one object or it's just more convenient to use that way, so as there's no reference about it you don't have to care that much, just use the query and be happy.
In my react app, I created an api.js file which creates an api object with axios.create and exports it as default. So, I use that template to make the API requests. The problem is that, one of the headers in created axios api object must be dynamic.
For example, see the locale header below:
At first it may be something like this:
export default api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
headers: {
post: {
"Content-Type": "application/json;charset=utf-8",
"Access-Control-Allow-Origin": "*",
locale: "en",
},
get: {
locale: "en",
},
},
});
But after some time it can be updated to some other locale, like "en" should be changed with "fr" for example. How can I update it, and make sure when it gets updated it changes in every place api is imported.
I can't use ContextApi etc, because I need to use that api in index.js file too, which, because of not being a react component, doesn't support use of hooks.
Sounds like a job for Axios interceptors...
import axios from "axios"
// some kind of storage for your locale
let locale = "en"
// some way of changing it
export const setLocale = (newLocale) => {
locale = newLocale
}
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
})
// register a synchronous request interceptor
api.interceptors.request.use(config => ({
...config,
headers: {
...config.headers,
locale // merge the "locale" into the request config headers
}
}), null, { synchronous: true })
export default api
Also, Access-Control-Allow-Origin is a response header that comes from the server. It does not belong in your request and in general will more than likely cause CORS errors.
Also, the default content-type when posting JS objects in Axios is application/json so you typically don't need to set it.
So, i'm doing an api, to query a certain map service which require an API key. I want to keep the API key private so in my own api on the server, I will call a http.request to the map service, then immediately pipe it into the response to my own api user.
Here is sample code to illustrate the idea:
import http from "http";
export default function handler(req, res) {
http.request(`http://map.service.example.com/foo-bar`, (mapRes) => mapRes.pipe(res));
}
But so far, the code above doesn't work.
Any other possible way (with fetch maybe?) is welcome.
For an http.request to go through you have to call its end method:
import http from "http";
export default function handler(req, res) {
const mapReq = http.request(`http://map.service.example.com/foo-bar`, (mapRes) => mapRes.pipe(res));
mapReq.end();
}
OR
You can use the get method:
import http from "http";
export default function handler(req, res) {
http.get(`http://map.service.example.com/foo-bar`, (mapRes) => mapRes.pipe(res));
}
So firstly i create custom axios instance with baseurl and export it like this:
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.BACKEND_URL,
});
instance.defaults.headers.common['Authorization'] = 'AUTH TOKEN';
instance.defaults.headers.post['Content-Type'] = 'application/json';
export default instance;
The problem is in my saga or in any component in general (ONLY client side) when importing this custom axios instance. I use next-redux-wrapper, and when i prefetch data (using getStaticProps) for my component everything works fine and the axios.defaults.baseURL property works just fine.
However the problem is on client-side, whenever i import the same axios instance in any component or in saga but i call it from lets say componentDidMount, the same axios.default.baseURL is undefined, so if i want to make get request i have to type in the full backend + queries URL. What could the problem be? EXAMPLE:
export function* fetchTPsSaga() {
try {
console.log(axios.defaults.baseURL);
const url = `/training-programs`;
const res = yield axios.get(url);
const tPs = res.data.data;
yield put(fetchTrainingProgramsSuccess(tPs));
} catch (err) {
yield put(fetchTrainingProgramsFail(err));
}
}
// The first time it renders (on server side), it's the valid baseURL property, however if i call the same saga from client-side (when component is rendered) it's UNDEFINED, so i have to type the full url
process.env only work on server-side. You can use publicRuntimeConfig to access environment variables both on client and server-side.
next.config.js
module.exports = {
publicRuntimeConfig: {
// Will be available on both server and client
backendUrl: process.env.BACKEND_URL,
},
}
axios instance file
import axios from 'axios';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const instance = axios.create({
baseURL: publicRuntimeConfig.backendUrl,
});
By the way, if you are using Next.js versions 9.4 and up, the Environment Variables provide another way.
Loading Environment Variables Rules
In order to expose a variable to the browser you have to prefix the variable with
NEXT_PUBLIC_
. For example:
NEXT_PUBLIC_BACKEND_URL='http://localhost:3000'
Then you can access this env variable in Axios as its client-side rendering
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
});
*Note: You have to use process.env.NEXT_PUBLIC_BACKEND_URL instead of process.env.BACKEND_URL