NestJs - How to get url of handler? - nestjs

I want to redirect user to another url in my server, but I do not want to hardcode url like res.redirect('/hello_world'). Instead I want to just specify handler's url of specified controller like res.redirect(HelloWorldController.handlerName.url) where HelloWorldContoller is
#Controller()
export class HelloWorldController {
#Get('hello_world')
handlerName(): string {
return 'Hello World!';
}
}

For example, you can use Reflect to get metadata like this:
import { PATH_METADATA } from '#nestjs/common/constants';
#Controller('api')
export class ApiController {
#Get('hello')
root() {
let routePath = Reflect.getMetadata(PATH_METADATA, StaticController);
routePath += '/' + Reflect.getMetadata(PATH_METADATA, StaticController.prototype.serveStatic);
console.log(routePath); will return `api/hello`
return {
message: 'Hello World!',
};
}
}
We get api metadata
Then it's methods
Why it returns api/hello if I need a path not of self url, but other
controller's url?
Here StaticController is used as an example, You can import any other controllers and pass it to Reflect.getMetadata(PATH_METADATA, AnyOtherController);

Related

How to use correctly NextJs API

i've just started using Next js with mongodb and i have a question about how should i organize the API route files.
I have a simple application that add, update and delete documents of a mongodb collection. For each operation i created a .ts file inside the api folder. Like this
And for example my new_task.ts file looks like this
export default async function AddTask (req:NextApiRequest, res:NextApiResponse) {
const task:Task = req.body
const client = await clientPromise;
const db = client.db("diary");
const myCollection: Collection = db.collection('tasks');
try {
await myCollection.insertOne(task)
res.send('Success')
} catch (error) {
res.status(400).json({error})
console.log(error)
}
}
Everything is working ok but i think it's kinda messy the file organization. Is there a way to put every operation inside just one file? Or to do so i would have to build a custom server with express?
Thanks
In one route function, you can check the request object req to see if the HTTP request method is GET POST PUT PATCH or DELETE. Depending on which method, you can call a different function.
Here is an example from the NextJS docs.
import type { NextApiRequest, NextApiResponse } from 'next'
export default function userHandler(req: NextApiRequest, res: NextApiResponse) {
const {
query: { id, name },
method,
} = req
switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ id, name: `User ${id}` })
break
case 'PUT':
// Update or create data in your database
res.status(200).json({ id, name: name || `User ${id}` })
break
default:
res.setHeader('Allow', ['GET', 'PUT'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
Another thing you can do to make your code more re-useable and easier to maintain is to write reusable function definitions in a lib folder and then import them into your api route files when you want to use them.
Have you tried creating a file in the lib folder and writing function definitions there for MongoDB and then importing those function definitions into your api route file?
Then call the appropriate function depending upon the request method.
In ./lib/mongodb, write a function definition and import any Mongo-related imports you need.
export async function updateUserInfo(parameters) {
// . . . your code needs to return something, probably an array or object from MongoDB
}
In your api route file, import that function definition.
import { updateUserInfo } from "../../lib/mongodb"
Inside your route function, call updateUserInfo and pass whatever arguments you need based on the parameters you put in the definition. Handle its return value using await.
import type { NextApiRequest, NextApiResponse } from 'next'
import { updateUserInfo } from "../../lib/mongodb"
export default function userHandler(req: NextApiRequest, res: NextApiResponse) {
const {
query: { id, name },
method,
} = req
switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ id, name: `User ${id}` })
break
case 'PUT':
// Update or create data in your database
const updateResult = await updateUserInfo( . . .)
// FIX THE OBJECT IN .JSON BELOW TO SUIT YOUR CODE
res.status(200).json({ id, name: name || `User ${id}` })
break
default:
res.setHeader('Allow', ['GET', 'PUT'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
You can reuse updateUserInfo anywhere you have arguments for the required parameters.
Also, consider when you are calling the API route. At build time or after. At build, you call from static functions and after you call from client-side.
So by using the lib file for function definitions, you can reuse them in server functions and static functions.
The structure of the files inside the api folder is your api architecture. So it's organization depends upon your application's needs. You can use static and dynamic routes, as you maybe already know.
Consider API best practices when designing your architecture.

NestJS - How to add a dynamic routing to the controller?

I have the following problem. Let's say I have an array of routes & paths to static resources, e.g.
const routes = [{ url: '/', path: 'assets/www' }]
What I would like to do is to create a set of dynamic routes to serve static resources. In express application I would do smth like:
const router = express.Router();
routes.forEach(route => {
router.use(route.url, express.static(path.join(__dirname, route.path)))
})
But is it possible to create such a logic inside a NestJS controller?
#Controller()
export class ItemsController {
constructor() {}
#Get()
findAll() {}
#Get(':id')
findOne() {}
....
}
As far as I can see all HTTP request handlers should be predefined using the corresponding decorators.
I would opt for an approach where you create your controllers fully dynamic. This is not natively supported in Nest, but you can actually create dynamic controllers and dynamic services using a mixin approach (what is actually quite common), also see this answer at GitHub:
import { Post, Get, Controller } from '#nestjs/common';
interface Options {
basePath: string
entityName: string // you could even pass down DTO classes here, for maximum flexibility.
providerName: string
}
// this service is generated dynamically as well
// preferably use a custom providerName.
interface DynamicService {
foo: () => void
bar: () => void
}
export const createDynamicController = (controllerOptions: Options) => {
#Controller(controllerOptions.basePath)
class MyController {
constructor(
#Inject(options.providerName) myProvider: DynamicProvider
){}
#Get(options.entityName)
findOne(){
return this.myProvider.foo()
}
#Post(options.entityName)
create(){
return this.myProvider.foo()
}
}
return MyController
}
Using that approach you can create all controllers in theory dynamically, but it asks a bit more about understanding of the NestJS dependency tree.
You can now create the controller like:
{
controllers: [createDynamicController({
basePath: 'foo',
entityName: 'barz',
providerName: 'custom-provider-name'
})
]
}
What you could do is have a dynamic controller -> with say Parameters identifiers -> the as if it were like an event handler id... (not really just using that as an way to think of the process.)
Then in your the connected service -> you can have the 5 crud operations however in your service you inject the static resources -> and use that Identifier to route the call.
query dto
export class QueryDto {
readonly params?: any[];
readonly body: any;
}
controller
#Post(':serviceId')
async create(#Param('serviceId') serviceId: string, #Body() queryDto: QueryDto) {
return await this.rootService.create(serviceId, queryDto);
}
inside your root service
// childService = used to route the request dynamically ->
// body = your body or if you dont need a body this is where any parameters would go. which is why it's any (for if it was a get)
// params last => incase your doing a put or something where you need a body and parameters -> but it also allows it to be optional.
async create(childService: string, data: any, params: any[]): Promise<any> {
if (!id) {
return await this[childService].create(data.body, ...data.params).exec()
}
just an idea, but if everything static then this should do it for you. just use the root service as a passthrough. from the single controller -> to the various services.

How can one upload an image to a KeystoneJS GraphQL endpoint?

I'm using TinyMCE in a custom field for the KeystoneJS AdminUI, which is a React app. I'd like to upload images from the React front to the KeystoneJS GraphQL back. I can upload the images using a REST endpoint I added to the Keystone server -- passing TinyMCE an images_upload_handler callback -- but I'd like to take advantage of Keystone's already-built GraphQL endpoint for an Image list/type I've created.
I first tried to use the approach detailed in this article, using axios to upload the image
const getGQL = (theFile) => {
const query = gql`
mutation upload($file: Upload!) {
createImage(file: $file) {
id
file {
path
filename
}
}
}
`;
// The operation contains the mutation itself as "query"
// and the variables that are associated with the arguments
// The file variable is null because we can only pass text
// in operation variables
const operation = {
query,
variables: {
file: null
}
};
// This map is used to associate the file saved in the body
// of the request under "0" with the operation variable "variables.file"
const map = {
'0': ['variables.file']
};
// This is the body of the request
// the FormData constructor builds a multipart/form-data request body
// Here we add the operation, map, and file to upload
const body = new FormData();
body.append('operations', JSON.stringify(operation));
body.append('map', JSON.stringify(map));
body.append('0', theFile);
// Create the options of our POST request
const opts = {
method: 'post',
url: 'http://localhost:4545/admin/api',
body
};
// #ts-ignore
return axios(opts);
};
but I'm not sure what to pass as theFile -- TinyMCE's images_upload_handler, from which I need to call the image upload, accepts a blobInfo object which contains functions to give me
The file name doesn't work, neither does the blob -- both give me server errors 500 -- the error message isn't more specific.
I would prefer to use a GraphQL client to upload the image -- another SO article suggests using apollo-upload-client. However, I'm operating within the KeystoneJS environment, and Apollo-upload-client says
Apollo Client can only have 1 “terminating” Apollo Link that sends the
GraphQL requests; if one such as apollo-link-http is already setup,
remove it.
I believe Keystone has already set up Apollo-link-http (it comes up multiple times on search), so I don't think I can use Apollo-upload-client.
The UploadLink is just a drop-in replacement for HttpLink. There's no reason you shouldn't be able to use it. There's a demo KeystoneJS app here that shows the Apollo Client configuration, including using createUploadLink.
Actual usage of the mutation with the Upload scalar is shown here.
Looking at the source code, you should be able to use a custom image handler and call blob on the provided blobInfo object. Something like this:
tinymce.init({
images_upload_handler: async function (blobInfo, success, failure) {
const image = blobInfo.blob()
try {
await apolloClient.mutate(
gql` mutation($image: Upload!) { ... } `,
{
variables: { image }
}
)
success()
} catch (e) {
failure(e)
}
}
})
I used to have the same problem and solved it with Apollo upload link. Now when the app got into the production phase I realized that Apollo client took 1/3rd of the gzipped built files and I created minimal graphql client just for keystone use with automatic image upload. The package is available in npm: https://www.npmjs.com/package/#sylchi/keystone-graphql-client
Usage example that will upload github logo to user profile if there is an user with avatar field set as file:
import { mutate } from '#sylchi/keystone-graphql-client'
const getFile = () => fetch('https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
{
mode: "cors",
cache: "no-cache"
})
.then(response => response.blob())
.then(blob => {
return new File([blob], "file.png", { type: "image/png" })
});
getFile().then(file => {
const options = {
mutation: `
mutation($id: ID!, $data: UserUpdateInput!){
updateUser(id: $id, data: $data){
id
}
}
`,
variables: {
id: "5f5a7f712a64d9db72b30602", //replace with user id
data: {
avatar: file
}
}
}
mutate(options).then(result => console.log(result));
});
The whole package is just 50loc with 1 dependency :)
The easies way for me was to use graphql-request. The advantage is that you don't need to set manually any header prop and it uses the variables you need from the images_upload_handler as de docs describe.
I did it this way:
const { request, gql} = require('graphql-request')
const query = gql`
mutation IMAGE ($file: Upload!) {
createImage (data:
file: $file,
}) {
id
file {
publicUrl
}
}
}
`
images_upload_handler = (blobInfo, success) => {
// ^ ^ varibles you get from tinymce
const variables = {
file: blobInfo.blob()
}
request(GRAPHQL_API_URL, query, variables)
.then( data => {
console.log(data)
success(data.createImage.fileRemote.publicUrl)
})
}
For Keystone 5 editorConfig would stripe out functions, so I clone the field and set the function in the views/Field.js file.
Good luck ( ^_^)/*

Nuxt: can't use beforeCreate or beforeMount to check data or redirect

I want to have the following in my nuxt page:
beforeCreate() {
if (!this.$store.getters['experiences/current']) {
this.$router.push('/experiences');
}
},
Not 100% sure if I should use beforeCreate or beforeMount but the idea is, if the experiences/current getter is empty, go to the index page.
If I console.log(this.$store.getters['experiences/current']) it is actually empty, but the redirect doesn't happen and a template rendering error rises because it relies on this.$store.getters['experiences/current'].
So how can I make data validation before first component render and redirect if data is not present?
You can use a js middleware instead ...
For ts implementation ...
import { Middleware } from '#nuxt/types'
const myMiddleware: Middleware = ({ store, redirect }) => {
if (!store.state.applicationState && !store.state.yourProperty) {
return redirect('/')
}
}
export default myMiddleware

How can I cast input JSON object to model class in Node.JS with TypeScript?

I am writing my Node.js server using TypeScript and express framework.
This is how my controller and route looks like:
export class AuthController {
public async signUpNewUser(request: Request, response: Response) {
...
}
}
How can I receive a model class instead Request type like in ASP.NET ?
Something like:
public async signUpNewUser(input: SignUpModel, response: Response) {
Is this a good idea at all? I am not sure this is a common approach in the Node.JS
I just want to make sure I get the same model each time and write a code related to this model and not on dynamic JSON object.
My suggestion is to convert to strong type model at the beginning of the route, but I am not sure this is a good way.
Does somebody have a solution for such cases?
How can I receive a model class instead Request type like in ASP.NET
This was a perpetual pain to me in my projects (and at work), eventually we decided to build a custom router with its own default error handling and auth-header checks. The trick with this pattern is to keep it lightweight, because this is still express and middleware is where things should go - this wrapper just provides a way for us to cast the express request into a properly shaped type based on the middleware we actually use.
This is pared down example, the idea is that you can specify the shape of the req & res by passing an interface (or an inlined type shape) and have typescript enforce the return shape.
Wrapper class example:
import * as express from 'express';
export type ExpressMethods = "get" | "post" | "put" | "delete" | "patch";
export type JsonRouteInput<RequestBody, RouteParams, QueryParams> = {
body: RequestBody;
params: RouteParams;
query: QueryParams;
};
export type JsonRouteHandler<
RequestBody,
RouteParams,
QueryParams,
ResponseBody
> = (
request: JsonRouteInput<RequestBody, RouteParams, QueryParams>
) => Promise<ResponseBody> | ResponseBody;
export class JsonRouter {
router = express.Router();
private addHandler<M extends ExpressMethods>(
method: M,
...middleware: express.RequestHandler[]
) {
this.router.use(...middleware);
}
get route(): {
[K in ExpressMethods]: <
RequestBody,
ResponseBody,
RouteParams = never,
QueryParams = never
>(
path: string,
handler: JsonRouteHandler<
RequestBody,
RouteParams,
QueryParams,
ResponseBody
>
) => JsonRouter
} {
const addables = {} as any;
(["get", "post", "put", "delete", "patch"] as ExpressMethods[]).forEach(
<RequestBody, ResponseBody, RouteParams = never, QueryParams = never>(
method
) => {
addables[method] = (
path: string,
handler: JsonRouteHandler<
RequestBody,
RouteParams,
QueryParams,
ResponseBody
>
) => {
this.router[method](path, async (req, res) => {
try {
const responseBody: ResponseBody = await handler({
body: req.body,
params: req.params,
query: req.query
});
res.json(responseBody);
} catch (err) {
// do your standard error handling or whatever
res.status(500).end("ow");
}
});
return this;
};
}
);
return addables;
}
}
And then using it
const jsonRouter = new JsonRouter().route.get<{ request: number }, { response: number }>(
"/hello-world",
req => {
return { response: req.body.request + 1 }; // type-checked result
}
);
This can definitely be taken one step further - I have some prototypes that allow us to semi-fluently build the shape of the request/response body. The goal with this strategy long term lets us generate a typescript rest client for the frontend, generate input-validation that matches the type we're using to annotate, and also enforce that the response is the right type - example router using this strategy to build the type dynamically
EDIT: To plug this example into an express server
const app = express();
// custom middleware goes here
app.use('/', jsonRouter.router);
app.listen(8000)
So you seem to have a couple different questions in there. The core question is "how do I cast a JSON object to a specific type", but then you also ask if it's a good idea or not and if it's a common practice.
The answer to your first question is pretty simple, you can cast it in your route (or wherever) like so:
router.get('/whatever', (req, res) => {
const signup: SignupModel = JSON.parse(req.model) as SignupModel;
// Do whatever you want with the signup model
});
Now, your other questions are way more opinion-based. If I'm being honest, I would say "don't use Typescript". :) Joking aside, I don't know how to answer your opinion-based question (nor is it a good fit for this site)

Resources