I am creating a CRUD in API,
but the delete does not seems to work properly.
I get a response from this
http://localhost:3000/api/admin/categories?id=1
and not from this
http://localhost:3000/api/admin/categories/1
this is the code in next.js:
export default async (req, res) => {
const {
query: { id },
method,
} = req;
switch (method) {
case "DELETE":
return res.status(200).json({
success: true,
id: id,
});
}
in React:
axios.delete(`http://localhost:3000/api/admin/categories/`, {id: 1})
The same situation is also with "PUT" method
Folder Directory:
api
|
---admin
--------categories
--------index.js
If you want to create API Routes such as DELETE, PUT ( they require /id as path parameter), you should create a separate file in that folder.
Like this:
api
|
---admin
--------categories
--------index.js
--------[id].js
And in the [id].js file:
export default function handler(req, res) {
const { id } = req.query;
if (req.method === "DELETE") {
res.end(`Category: ${id}`);
}
}
Related
Is this possible to add the condition directly in the route itself?
Something like this...
...
Router.get('/:status(active|inactive)', index);
...
I know that it can be handled by middleware or in the index method via conditions. However, If this is possible then this way can save a lot of efforts.
Currently I am writing this way:
routes/category.js
Router.get('/:status?', index);
Category/Controller.js
...
const index = async (req, res) => {
try {
const pageParams = paginationParams(req.query.page, configs.perPage);
const conditions = (typeof req.params.status === 'undefined') ? {} : (
['active', 'inactive'].includes(req.params.status) ? req.params.status : {}
);
const count = await Model.count(conditions);
const items = await Model.find(conditions, {
__v: false,
}, {
sort: {
status: -1,
title: 1,
}
})
.skip(pageParams.serialNumber)
.limit(configs.perPage);
...
} catch (error) {
res.status(500).send();
}
};
...
Actually, Router.get('/:status(active|inactive)', index); should work as expected by restricting the possible values for the status parameter to either active or inactive.
You should then be able to retrieve the status parameter as usual in your function with:
const { status } = req.params;
If you don't specify the status parameter as active or inactive on your request URL, the page will 404.
For more, you can read this interesting article on the topic.
I Found The Tutorial about
Designing a clean REST API with Node.js (Express + Mongo)
project in github.
but the problem is i didn't get the concept of routing in one part.
the misundrestanding part is how is it possible to pass httpRequest data to handle method within contact-endpoint module?
because handle method is in here export default function makeContactsEndpointHandler({ contactList }) {
return async function handle(httpRequest) {
this is the index of project:
import handleContactsRequest from "./contacts";
import adaptRequest from "./helpers/adapt-request";
app.all("/contacts", contactsController);
app.get("/contacts/:id", contactsController);
function contactsController(req, res) {
const httpRequest = adaptRequest(req);
handleContactsRequest(httpRequest)
.then(({ headers, statusCode, data }) =>
res.set(headers).status(statusCode).send(data)
)
.catch((e) => res.status(500).end());
}
this is the adaptRequest:
export default function adaptRequest (req = {}) {
return Object.freeze({
path: req.path,
method: req.method,
pathParams: req.params,
queryParams: req.query,
body: req.body
})
}
this is the handleContactsRequest module:
import makeDb from "../db";
import makeContactList from "./contact-list";
import makeContactsEndpointHandler from "./contacts-endpoint";
const database = makeDb();
const contactList = makeContactList({ database });
const contactsEndpointHandler = makeContactsEndpointHandler({ contactList });
export default contactsEndpointHandler;
this is part of contact-endpoint module:
export default function makeContactsEndpointHandler({ contactList }) {
return async function handle(httpRequest) {
switch (httpRequest.method) {
case "POST":
return postContact(httpRequest);
case "GET":
return getContacts(httpRequest);
default:
return makeHttpError({
statusCode: 405,
errorMessage: `${httpRequest.method} method not allowed.`,
});
}
}
makeContactsEndpointHandler is a function that returns a function (async handle(xxx)).
In handleContactsRequest, we export the result of the call: makeContactsEndpointHandler({ contactList }). Which is therefore the function async handle(xxx) itself.
So, in index, when we call handleContactsRequest with the constant httpRequest as argument, we're actually calling that handle(xxx) function. (I wrote xxx as parameter name to highlight the difference between the two httpRequest declarations.)
I'm developing a site with multiple languages. Some routes will therefore also have to be localized and I'm not sure how to do this properly.
I'm using #koa/router for routing.
For this example it's only English and Swedish but the site will handle more languages.
I can setup routes to match words in different languages like
router.get('/(create-account|skapa-konto)/', (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
});
But, I want the English site to only respond to '/sign-up' and send 404 for '/skapa-konto' (and vice versa).
In the real world the route would point to some controller function. So if I set up individual routes for each language I would have to change all localized routes manually should the controller function change in the future. That's something I would like to avoid ;)
Any suggestions?
I ended up solving this by extending the Router like this:
const LocalizedRouter = class extends Router {
/**
* Set up route mapping
* #param {object} options
*/
constructor(options) {
if (!Array.isArray(options.languages)) {
throw new TypeError('Languages must be of type Array');
}
super(options);
this.languages = options.languages;
}
/**
* Router function for GET method
* #param {string | Object<string, string>} RouteCollection
*/
get(routes, func) {
if (typeof(routes) === 'string') {
super.get(routes, func);
return;
}
if (typeof(routes) === 'object') {
for(const key in routes) {
if(!this.languages.includes(key)) {
continue;
}
if(typeof(func) !== 'function') {
throw new TypeError('Middleware must be a function');
}
const checkLanguageAndMount = async (ctx, next) => {
if(ctx.state.lang !== key) {
return next();
}
return func(ctx, next);
};
super.get(routes[key], checkLanguageAndMount);
}
return;
}
throw new TypeError('"Routes" must be a string or an object');
}
};
I can then set up my routes like this:
const myRouter = new LocalizedRouter({
languages: ['en', 'sv']
});
myRouter.get({
'en': '/create-account',
'sv': '/skapa-konto'
}, (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
};
This can probably be cleaned up but it does solve what I wanted to do.
EDIT: Fixed bug that caused 404 if two languages had identical paths
This problem interested me so I created a small github repo with some code. I'll try to explain here:
I created an array with some options:
const localeConfig = [
{
locale: "en",
routes: [
{
path: "/sign-up",
controllers: [enController],
method: "GET",
},
],
prefix: false,
},
{
locale: "se",
routes: [
{
path: "/skapa-konto",
controllers: [seController],
method: "GET",
},
],
prefix: false,
},
];
I then pass this object to a setupRoutes function that basically iterates the array, generating all the routes according to those options.
const setupRoutes = (localeConfig) => {
// Have some check to prevent duplicate routes
localeConfig.forEach((opt) => {
// Adding prefix according to option
const localePrefix = opt.prefix ? `/${opt.locale}` : "";
opt.routes.forEach((route) => {
const path = `${localePrefix}${route.path}`;
router[route.method.toLowerCase()].apply(router, [
path,
...route.controllers,
]);
});
});
};
So, for instance, if you were to change any of the controllers in either language you would only need to update the specific locale object.route.controllers. I imagine you could even have each different locale in a different file to have some modularity.
The github repo is here and I would really like to have you contribute to it if you have any idea on how to improve this.
Cheers!
I am trying to create an app and within the app the user can install a theme, however, I can't seem to work out why the theme is not being created. It keeps pulling the themes already installed on my store to the console, my code doesn't seem to create a theme that would show up on my shopify store.
server.js
router.post('/api/theme', async (ctx) => {
try {
const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/themes.json", {
headers: {
'X-Shopify-Access-Token': ctx.cookies.get('accessToken')
},
})
.then(response => response.json())
.then(json => {
console.log("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2020-01/themes.json", json);
});
ctx.body = {
data: results
};
} catch (err) {
console.log(err)
}
});
frontend .js file
async function getUser() {
var url = `/api/theme`;
var method = 'post';
const theme = {
theme: {
name: "Lemongrass",
src: "https://codeload.github.com/Shopify/skeleton-theme/zip/master"
}
};
const data = JSON.stringify(theme);
fetch(url, { method: method, body: data})
}
In order to create a theme you need a zip archive of the theme you like to create.
The end point should be /admin/api/2020-01/themes.json and the body should be something like this:
{
"theme": {
"name": "Theme name",
"src": "http://themes.shopify.com/theme.zip",
"role": "unpublished"
}
}
Please refer to https://shopify.dev/docs/admin-api/rest/reference/online-store/theme#create-2020-01 for more information.
At the moment from your code I don't see neither the correct POST request, neither the archive file.
When I pass selected routes from the database in the promise then the authourization doesn't work. That means the requests for passed routes are always authorized.
protected applyRoutes(consumer: MiddlewaresConsumer) {
let paths = this.authPathService.findAll();
paths.then((resultPaths) => {
let result: {}[] = [];
for (let path of resultPaths) {
result.push({
path: path.path,
method: RequestMethod.ALL
})
}
consumer
.apply(passport.authenticate('jwt', { session: false }))
.forRoutes(...result);
return result;
}, (error) => {
console.log('error', error);
});
}
It works well when i pass routes in an object array
protected applyRoutes(consumer: MiddlewaresConsumer) {
consumer
.apply(passport.authenticate('jwt', { session: false }))
.forRoutes(...[
{ path: '/auth/authorized', method: RequestMethod.ALL },
{ path: '/auth/test', method: RequestMethod.ALL }]);
}
It's impossible to asynchronously apply middlewares using MiddlewaresConsumer. Instead, register an async component (https://docs.nestjs.com/fundamentals/async-components) that will fetch all paths, for example as AUTH_PATHS, then inject it to your module class, let's say AuthModule and use this array inside configure() method.