I created a simple api endpoint named getFeed which is supposed to get feed content from Sanity CMS. But unexpectedly the endpoint is throwing an error "res.status is not a function". I know there is a similar question asked here , but in my case the api endpoint file is stored in the supposed pages/api directory. Here is the code snippet below.
import { NextApiResponse } from 'next'
import { client } from '../../lib/sanity/sanity'
export default async function getFeed(res: NextApiResponse) {
try {
const feeds = await client.fetch(
`*[_type == "post"]
{
_createdAt,
title,
description,
picture,
postDocId,
postedByUserId,
postedByUserName,
postedByUserImage
}`
)
return res.status(200).json({ feeds })
} catch (error) {
return res.status(500).json({ message: "Couldn't get post feed:\n", error })
}
}
Here is my folder structure
What am I doing wrong??
Try to specify also the req parameter and add a type to the response:
import { NextApiRequest, NextApiResponse } from 'next'
interface Data {
message?: string;
feeds?: <type-of-feeds>[];
}
export default async function getFeed(req: NextApiRequest, res: NextApiResponse<Data>) { ... }
I would like some help on an API issue.
I have been trying to link each Article page based on the content I have created in Strapi CMS on my local server.
The API endpoint that I manage to gather data is from 'http://localhost:1337/api/articles?populate=*'.
Here is my code:
// lib/api.js
export class ApiError extends Error {
constructor(url, status) {
super(`'${url}' returned ${status}`);
if(Error.captureStackTrace) {
Error.captureStackTrace(this, ApiError);
}
this.name = 'ApiError';
this.status = status;
}
}
export async function fetchJson(url, options) {
const response = await fetch(url, options);
if(!response.ok) {
throw new ApiError(url, response.status);
}
return await response.json();
}
// lib/articles.js
import { fetchJson } from "./api";
const API_URL = process.env.API_URL;
// Gets a single article
export async function getArticle(id) {
const article = await fetchJson(`${API_URL}/api/article/${id}`);
return stripArticle(article);
}
// Gets all articles
export async function getArticles() {
const articles = await fetchJson(`${API_URL}/api/articles`);
return articles.map(stripArticle);
}
function stripArticle(article) {
return {
id: article.id,
title: article.attributes.Title,
content: article.attributes.Content,
pictureUrl: API_URL + article.attributes.Photo.formats.thumbnail.url,
}
}
Article Page:
//article/[id].js
import Page from "../../components/Page";
import { getArticle, getArticles } from "../../lib/articles";
import ReactMarkdown from 'react-markdown';
import Moment from 'react-moment';
export async function getStaticProps({ params }) {
const article = await getArticle(params.id)
return {
props: { article },
unstable_revalidate: 1,
}
}
export default function Article({ article }) {
return (
<Page title={article.Title}>
<ReactMarkdown source={article.Content} />
<p>
<Moment from="MM Do YYYY">{article.CreatedAt}</Moment>
</p>
</Page>
)
}
export async function getStaticPaths() {
const articles = await getArticles()
return {
paths: articles.map((article) => ({
params: { id: article.id.toString() }, // Number convert to string
})),
fallback: 'blocking', // What if error. Client is blocked, until new page is ready.
};
}
I would get an error: TypeError: articles.map is not a function.
If there is a better way to format and write the code, do let me know as I have been trying to find which is best.
Thanks for the help in advance.
I am a new one in the apollo world. I use Vue composition API and #vue/apollo-composable with version V4 for my vue apollo client. And the backend is nodejs server with apollo server.
Now I have a problem on the login page with useQuery, if I call the result of the query, they will be show the error
TS2349: This expression is not callable. Type 'Ref' has no call signatures
import { ref, defineComponent } from '#vue/composition-api'
import gql from 'graphql-tag'
import { useUserLogin } from '#/mixins/user-login'
import { CustomRules } from '#/validators/rules'
import { useQuery, useMutation } from '#vue/apollo-composable'
export default defineComponent({
props: {
msg: String,
},
setup(props, { root }) {
const form = ref()
const rules = ref(CustomRules)
const username = ref('')
const password = ref('')
const errorMessage = ref('')
const { result: loginBenutzer } = useQuery(gql`
query loginBenutzer($kuerzel: String!, $passwort: String!) {
loginBenutzer(kuerzel: $kuerzel, passwort: $passwort) {
user {
kuerzel: BEN_MIA_KUERZEL,
name: BEN_NAME
},
token
}
}
`)
function login() {
if (form.value.validate()) {
loginBenutzer({ kuerzel: username.value, passwort: password.value })
.then(data => {
useUserLogin(data.data.loginBenutzer)
root.$router.push('/hm')
})
.catch(err => {
errorMessage.value = err.message
console.log(err)
})
}
}
return {
form,
rules,
username,
password,
login,
errorMessage,
}
},
})
Calling to result: loginBenutzer of this line
loginBenutzer({ kuerzel: username.value, passwort: password.value })
the loginBenutzer shows the error:
TS2349: This expression is not callable. Type 'Ref' has no call signatures
And in the Apollo server type is defined like this
type Query {
loginBenutzer(kuerzel: String!, passwort: String!): LoginResponse!,
}
but if I change the query to mutation, then they are working. Like this
const { mutate: loginBenutzer } = useMutation(gql`
mutation loginBenutzer($kuerzel: String!, $passwort: String!) {
loginBenutzer(kuerzel: $kuerzel, passwort: $passwort) {
user {
kuerzel: BEN_MIA_KUERZEL,
name: BEN_NAME
},
token
}
}
`)
function login() {
if (form.value.validate()) {
loginBenutzer({ kuerzel: username.value, passwort: password.value })
.then(data => {
useUserLogin(data.data.loginBenutzer)
root.$router.push('/hm')
})
.catch(err => {
errorMessage.value = err.message
console.log(err)
})
}
}
and the type like this
type Mutation {
loginBenutzer(kuerzel: String!, passwort: String!): LoginResponse!,
}
but I am very sure, the useQuery for the calling of user information is a right way.
result: result data object.
As documentation said,result return result data object, which will be a Ref type,
refetch(variables?): Execute the query again, optionally with new
variables.
Try refetch instead if you want to pass new variables
I am trying to upload multiple files with nestjs using the fastify adapter. I can do so following the tutorial in this link -article on upload
Now this does the job of file upload using fastify-multipart, but I couldnt make use of the request validations before uploading,
for example, here is my rule-file-models (which later I wanted to save to postgre)
import {IsUUID, Length, IsEnum, IsString, Matches, IsOptional} from "class-validator";
import { FileExtEnum } from "./enums/file-ext.enum";
import { Updatable } from "./updatable.model";
import {Expose, Type} from "class-transformer";
export class RuleFile {
#Expose()
#IsUUID("4", { always: true })
id: string;
#Expose()
#Length(2, 50, {
always: true,
each: true,
context: {
errorCode: "REQ-000",
message: `Filename shouldbe within 2 and can reach a max of 50 characters`,
},
})
fileNames: string[];
#Expose()
#IsEnum(FileExtEnum, { always: true, each: true })
fileExts: string[];
#IsOptional({each: true, message: 'File is corrupated'})
#Type(() => Buffer)
file: Buffer;
}
export class RuleFileDetail extends RuleFile implements Updatable {
#IsString()
#Matches(/[aA]{1}[\w]{6}/)
recUpdUser: string;
}
And I wanted to validate the multipart request and see if these are set properly.
I cannot make it to work with event subscription based approach. Here are a few things I tried - adding the interceptor, to check for the request
#Injectable()
export class FileUploadValidationInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req: FastifyRequest = context.switchToHttp().getRequest();
console.log('inside interceptor', req.body);
// content type cmes with multipart/form-data;boundary----. we dont need to valdidate the boundary
// TODO: handle split errors based on semicolon
const contentType = req.headers['content-type'].split(APP_CONSTANTS.CHAR.SEMI_COLON)[0];
console.log(APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType));
const isHeaderMultipart = contentType != null?
this.headerValidation(contentType): this.throwError(contentType);
**// CANNOT check fir req.file() inside this, as it throws undefined**
return next.handle();
}
headerValidation(contentType) {
return APP_CONSTANTS.REGEX.MULTIPART_CONTENT_TYPE.test(contentType) ? true : this.throwError(contentType);
}
throwError(contentType: string) {
throw AppConfigService.getCustomError('FID-HEADERS', `Request header does not contain multipart type:
Provided incorrect type - ${contentType}`);
}
}
I wasnt able to check req.file() in the above interceptor. It throws as undefined. I tried to follow the fastify-multipart
But I wasnt able to get the request data in a prehandler as provided in the documentation for fastify-multipart
fastify.post('/', async function (req, reply) {
// process a single file
// also, consider that if you allow to upload multiple files
// you must consume all files othwise the promise will never fulfill
const data = await req.file()
data.file // stream
data.fields // other parsed parts
data.fieldname
data.filename
data.encoding
data.mimetype
// to accumulate the file in memory! Be careful!
//
// await data.toBuffer() // Buffer
//
// or
await pump(data.file, fs.createWriteStream(data.filename))
I tried getting via by registering a prehandler hook of my own like this (executed as iife)
(async function bootstrap() {
const appConfig = AppConfigService.getAppCommonConfig();
const fastifyInstance = SERVERADAPTERINSTANCE.configureFastifyServer();
// #ts-ignore
const fastifyAdapter = new FastifyAdapter(fastifyInstance);
app = await NestFactory.create<NestFastifyApplication>(
AppModule,
fastifyAdapter
).catch((err) => {
console.log("err in creating adapter", err);
process.exit(1);
});
.....
app.useGlobalPipes(
new ValidationPipe({
errorHttpStatusCode: 500,
transform: true,
validationError: {
target: true,
value: true,
},
exceptionFactory: (errors: ValidationError[]) => {
// send it to the global exception filter\
AppConfigService.validationExceptionFactory(errors);
},
}),
);
app.register(require('fastify-multipart'), {
limits: {
fieldNameSize: 100, // Max field name size in bytes
fieldSize: 1000000, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 100000000000, // For multipart forms, the max file size
files: 3, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
},
});
(app.getHttpAdapter().getInstance() as FastifyInstance).addHook('onRoute', (routeOptions) => {
console.log('all urls:', routeOptions.url);
if(routeOptions.url.includes('upload')) {
// The registration actually works, but I cant use the req.file() in the prehandler
console.log('###########################');
app.getHttpAdapter().getInstance().addHook('preHandler', FilePrehandlerService.fileHandler);
}
});
SERVERADAPTERINSTANCE.configureSecurity(app);
//Connect to database
await SERVERADAPTERINSTANCE.configureDbConn(app);
app.useStaticAssets({
root: join(__dirname, "..", "public"),
prefix: "/public/",
});
app.setViewEngine({
engine: {
handlebars: require("handlebars"),
},
templates: join(__dirname, "..", "views"),
});
await app.listen(appConfig.port, appConfig.host, () => {
console.log(`Server listening on port - ${appConfig.port}`);
});
})();
Here is the prehandler,
export class FilePrehandlerService {
constructor() {}
static fileHandler = async (req, reply) => {
console.log('coming inside prehandler');
console.log('req is a multipart req',await req.file);
const data = await req.file();
console.log('data received -filename:', data.filename);
console.log('data received- fieldname:', data.fieldname);
console.log('data received- fields:', data.fields);
return;
};
}
This pattern of registring and gettin the file using preHandler works in bare fastify application. I tried it
Bare fastify server:
export class FileController {
constructor() {}
async testHandler(req: FastifyRequest, reply: FastifyReply) {
reply.send('test reading dne');
}
async fileReadHandler(req, reply: FastifyReply) {
const data = await req.file();
console.log('field val:', data.fields);
console.log('field filename:', data.filename);
console.log('field fieldname:', data.fieldname);
reply.send('done');
}
}
export const FILE_CONTROLLER_INSTANCE = new FileController();
This is my route file
const testRoute: RouteOptions<Server, IncomingMessage, ServerResponse, RouteGenericInterface, unknown> = {
method: 'GET',
url: '/test',
handler: TESTCONTROLLER_INSTANCE.testMethodRouteHandler,
};
const fileRoute: RouteOptions = {
method: 'GET',
url: '/fileTest',
preHandler: fileInterceptor,
handler: FILE_CONTROLLER_INSTANCE.testHandler,
};
const fileUploadRoute: RouteOptions = {
method: 'POST',
url: '/fileUpload',
preHandler: fileInterceptor,
handler: FILE_CONTROLLER_INSTANCE.fileReadHandler,
};
const apiRoutes = [testRoute, fileRoute, fileUploadRoute];
export default apiRoutes;
Could someone let me know the right the way to get the fieldnames , validate them befr the service being called in Nestjs
Well, I have done something like this and It works great for me. Maybe it can work for you too.
// main.ts
import multipart from "fastify-multipart";
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.register(multipart);
// upload.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
BadRequestException,
} from "#nestjs/common";
import { FastifyRequest } from "fastify";
#Injectable()
export class UploadGuard implements CanActivate {
public async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest() as FastifyRequest;
const isMultipart = req.isMultipart();
if (!isMultipart)
throw new BadRequestException("multipart/form-data expected.");
const file = await req.file();
if (!file) throw new BadRequestException("file expected");
req.incomingFile = file;
return true;
}
}
// file.decorator.ts
import { createParamDecorator, ExecutionContext } from "#nestjs/common";
import { FastifyRequest } from "fastify";
export const File = createParamDecorator(
(_data: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest() as FastifyRequest;
const file = req.incomingFile;
return file
},
);
// post controller
#Post("upload")
#UseGuards(UploadGuard)
uploadFile(#File() file: Storage.MultipartFile) {
console.log(file); // logs MultipartFile from "fastify-multipart"
return "File uploaded"
}
and finally my typing file
declare global {
namespace Storage {
interface MultipartFile {
toBuffer: () => Promise<Buffer>;
file: NodeJS.ReadableStream;
filepath: string;
fieldname: string;
filename: string;
encoding: string;
mimetype: string;
fields: import("fastify-multipart").MultipartFields;
}
}
}
declare module "fastify" {
interface FastifyRequest {
incomingFile: Storage.MultipartFile;
}
}
So I found a simpler alternative. I started using fastify-multer. I used it along with this awesome lib - which made me use the multer for fastify - #webundsoehne/nest-fastify-file-upload
These are the changes I made. I registered the multer content process.
app.register(multer( {dest:path.join(process.cwd()+'/upload'),
limits:{
fields: 5, //Number of non-file fields allowed
files: 1,
fileSize: 2097152,// 2 MB,
}}).contentParser);
Then in the controller - I use it as the nestjs doc says . This actually makes fasitfy work with multer
#UseInterceptors(FileUploadValidationInterceptor, FileInterceptor('file'))
#Post('/multerSample')
async multerUploadFiles(#UploadedFile() file, #Body() ruleFileCreate: RuleFileCreate) {
console.log('data sent', ruleFileCreate);
console.log(file);
// getting the original name of the file - no matter what
ruleFileCreate.originalName = file.originalname;
return await this.fileService.fileUpload(file.buffer, ruleFileCreate);
}
BONUS - storing the file in local and storing it in DB - Please refer
github link
I'm trying to make test development with Angular 6 and GraphQl but we really don't know how to do as the best way possible. I have tried to find something on the internet that explains this, but nothing really good has been found.
I'll post my case here looking for someone who could help me to do, or someone who could tell me any tutorial/web to find more and good information.
The problem
I want to test an Auth. I have an auth.service.js and the respective spec.js. You can see it below:
AUTH_SERVICE_TS
import { Injectable } from '#angular/core';
import { Store } from '#ngrx/store';
import * as UserActions from './../../store/user/actions';
import gql from 'graphql-tag';
import { Apollo } from 'apollo-angular';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class AuthService {
user;
constructor(private store: Store<any>, private apollo: Apollo, private router: Router) {
this.store.select('state').subscribe(state => this.user = state);
}
/**
* Function that check the email and password for login
* #param email
* #param password
*/
login(email: string, password: string) {
this.apollo.mutate({
mutation: this.loginRequestGql(),
variables: {
email: email,
password: password
}
}).subscribe(value => {
const data = value.data.login;
this.saveUserData(data);
this.router.navigate(['/app']);
});
}
/**
* Function that save user data in the store and in the session storage
* #param data
*/
saveUserData(data) {
this.store.dispatch(new UserActions.Login({token: data.token}));
this.setSessionStorage(this.user);
}
/**
* Function that remove user info in the store
*/
logout() {
this.store.dispatch(new UserActions.Logout());
this.setSessionStorage(this.user);
}
/**
* Function that create the request with Graphql sintax
*/
loginRequestGql() {
return gql`
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`;
}
/**
* Function that save in the session storage the data parameter
* #param data
*/
setSessionStorage(data) {
sessionStorage.setItem('session', JSON.stringify(data));
}
}
AUTH_SERVICE_SPEC_TS
import { TestBed, inject } from '#angular/core/testing';
import { ApolloTestingController, ApolloTestingModule } from "apollo-angular/testing";
import { RouterTestingModule } from '#angular/router/testing';
import { AuthService } from './auth.service';
import { Store, StoreModule } from '#ngrx/store';
import { reducer } from '../../store/user/reducer';
describe('AuthService', () => {
let backend: ApolloTestingController;
let authService: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule, ApolloTestingModule, StoreModule.forRoot({ state: reducer }) ],
providers: [ AuthService,
{ provide: ApolloTestingModule, useClass: ApolloTestingModule }
]
});
});
beforeEach(() => {
backend = TestBed.get(ApolloTestingController);
authService = TestBed.get(AuthService);
});
it('should be created', inject([AuthService], (service: AuthService) => {
expect(service).toBeTruthy();
}));
it('should test login', (done) => {
const email = 'diego#mail.com';
const password = '123456';
// const a = authService.login(email, password);
// expect(a).toBe(TEST_RESPONSE['data'].login.token);
// authService.login(email, password);
// backend.expectOne(authService.loginRequestGql).flush(TEST_RESPONSE);
});
});
const TEST_RESPONSE: Object = {
"data": {
"login": {
"token": "eyJ0eXAiOiJKLCJhbGciOiJSUzI1NiIsImp0aSI6IjZjZDBjMDMXX0.as7-r_nlYfJ2w3CfOqwtLcTlBg5LrwFcm_ZXZ_GzCl5Qq0GS92r5tqGJtFzRfG02PPoLZ8uwsbgLj-5v2pYBXHjBLZvbjnW_zgXRLoDEcrBDpfPAoVH85ca_hb_xVaIgEUGumUPfn2IOx0Ce8fLlqtWGqoWtWzcCE
}
};
Thanks in advance to the community!! Hope you can help me!!
PD: If you need more information, just request and i'll give.
In one of recent versions of apollo-angular we released a testing utilities. Testing technique is pretty much similar to how you test HttpClient in Angular.
To learn more about how to test components and services that uses Apollo, please read the official documentation about it.
https://www.apollographql.com/docs/angular/guides/testing.html
It seems we cannot use expectOne with a simple DocumentNode parameter when doing a mutation.
So instead of:
backend.expectOne(authService.loginRequestGql).flush(TEST_RESPONSE);
we must pass to expectOne a function which asserts the operation's query definition is the expected one:
backend.expectOne((operation) => {
expect(operation.query.definitions).toEqual(mutation.definitions);
return true;
})
.flush(TEST_RESPONSE);