Pretty new to GraphQL, I am facing an issue with the latest version of NestJS where I am currently trying to add a mutation to a resolver that doesn't show in the playground when the server is running.
It looks like the GraphQL schema is not updated on server launch.
The createUser mutation is showing in the GraphQL playground and working but the getUsers one (created for test purposes) is not showing.
I would appreciate any hint on how to tackle this issue.
Importation of the GraphQLMOdule in app.module
import { Module } from '#nestjs/common';
// Libraries
import { TypeOrmModule } from '#nestjs/typeorm';
import { GraphQLModule } from '#nestjs/graphql';
// App modules
import { MealModule } from './meal/meal.module';
import { AuthModule } from './auth/auth.module';
// Entities
import { MealEntity } from './meal/meal.entity';
import { UserEntity } from './auth/user.entity';
#Module({
imports: [
TypeOrmModule.forRoot({
type: 'mongodb',
url: 'mongodb://localhost/sideproject',
synchronize: true,
useUnifiedTopology: true,
entities: [MealEntity, UserEntity],
}),
GraphQLModule.forRoot({
autoSchemaFile: true,
debug: true,
playground: true
}),
MealModule,
AuthModule,
],
})
export class AppModule {}
Here are the types for the user module I am experiencing difficulties with :
import { ObjectType, Field, ID } from '#nestjs/graphql';
#ObjectType('User')
export class UserType {
#Field(() => ID)
id: string;
#Field()
username: string;
#Field()
email: string;
#Field()
password: string;
}
The associated resolver :
import { Resolver, Mutation, Args, Query } from '#nestjs/graphql';
import { UserType } from './types/user.types';
import { CreateUserInputType } from './inputs/create-user.input';
import { UserEntity } from './user.entity';
import { AuthService } from './auth.service';
#Resolver(of => UserType)
export class AuthResolver {
constructor(private authService: AuthService) {}
#Mutation(returns => UserType)
signUp(
#Args('createUserInput') createUserInput: CreateUserInputType,
): Promise<UserEntity> {
return this.authService.signUp(createUserInput);
}
#Query(returns => [UserType])
getUsers(): Promise<UserEntity[]> {
return this.authService.getUsers()
}
}
The service :
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { CreateUserInputType } from './inputs/create-user.input';
import { UserRepository } from './user.repository';
import { UserEntity } from './user.entity';
#Injectable()
export class AuthService {
constructor(
#InjectRepository(UserRepository)
private userRepository: UserRepository,
) {}
signUp(createUserInput: CreateUserInputType): Promise<UserEntity> {
return this.userRepository.signUp(createUserInput);
}
async getUsers(): Promise<UserEntity[]> {
return await this.userRepository.find();
}
}
And finally the repository for the user module :
import { Repository, EntityRepository } from 'typeorm';
import { UserEntity } from './user.entity';
import { InternalServerErrorException } from '#nestjs/common';
import { CreateUserInputType } from './inputs/create-user.input';
import { v4 as uuid } from 'uuid';
import * as bcrypt from 'bcryptjs';
#EntityRepository(UserEntity)
export class UserRepository extends Repository<UserEntity> {
async signUp(createUserInput: CreateUserInputType): Promise<UserEntity> {
const { username, email, password } = createUserInput;
const user = this.create();
user.id = uuid();
user.username = username;
user.email = email;
user.password = bcrypt.hashSync(password, bcrypt.genSaltSync(12));
try {
return await this.save(user);
} catch (err) {
console.log(err);
throw new InternalServerErrorException();
}
}
}
Thank you very much !
I think you need to add the resolver to the providers collection for the resolvers to be available.
Had the same issue and these steps solved the problem:
stop the server
remove the dist folder
restart the server
The answer to this is : declaring the objectype only will not update the schema. including the objectype as a return type within a resolver is the key.
In my case (using Docker), I had to clear cached volumes with:
docker system prune -a
After rebuild, schema.gql was updated.
Related
I am using TypeORM in nestJS project which has users and each user has a cart.
I have One-To-One relation between the users table and the carts table.
If I create a user I want the Cart to be automatically created and inserted into carts table and get its id and insert it into users table with the created user's info.
My question is, is there any way I can do that from the UserService
I have this User entity
import { Cart } from 'src/cart/cart.entity';
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from 'typeorm';
#Entity()
export class User {
#PrimaryGeneratedColumn('increment')
id: number;
#OneToOne(() => Cart, cart => cart.id)
cart_id: number;
#Column()
username: string;
#Column()
email: string;
#Column({ default: true })
password: string;
}
Also this Cart entity
import { CartItem } from 'src/cart_item/cart_item.entity';
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
#Entity()
export class Cart {
#PrimaryGeneratedColumn('increment')
id: number;
#Column({ default: 0.00 })
total_price: number;
#OneToMany(
type => CartItem,
cartItem => cartItem.cart,
)
cartItem: CartItem[];
}
This is the user service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
#Injectable()
export class UserService {
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOne(id);
}
async remove(id: number): Promise<void> {
await this.usersRepository.delete(id);
}
async add(user: User){
// write code here to create and insert a cart
// then get the cart id
// then add the id to "user" to be inserted
await this.usersRepository.insert(user);
}
}
this is the cart service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cart } from './cart.entity'
#Injectable()
export class CartService {
constructor(
#InjectRepository(Cart)
private cartsRepository: Repository<Cart>
) { }
findOne(id: number): Promise<Cart> {
return this.cartsRepository.findOne(id)
}
findAll(): Promise<Cart[]> {
return this.cartsRepository.find()
}
async remove(id: number): Promise<void> {
await this.cartsRepository.delete(id);
}
public async create(cart: Cart) {// I want to call this function in UserService
await this.cartsRepository.insert(cart);
}
}
Trying to build NestJS + GraphQL + TypeORM backend and I am stuck on trying to create mutations and queries. Tutorials people solve the problem in exactly this way, but still I can't understand what the error is.
Error: Cannot determine a GraphQL output type for the "createUser". Make sure your class is decorated with an appropriate decorator.
All decorators seem to be okay. Anyone have same problem?
user.module.ts
import { Module } from '#nestjs/common';
import { UserService } from './user.service';
import { UsersResolver } from './user.resolver';
import { TypeOrmModule } from '#nestjs/typeorm';
import { User } from './user.entity';
#Module({
imports: [TypeOrmModule.forFeature([User])],
exports:[TypeOrmModule],
providers: [UserService,UsersResolver ],
})
export class UserModule {}
user.entity.ts
import {
Column,
Entity,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Post } from '../post/post.entity';
import { Comment } from '../comment/comment.entity';
import { Field, ObjectType } from '#nestjs/graphql';
enum RoleEnum {
Admin = 'Admin',
Author = 'Author',
}
#Entity()
#ObjectType()
export class User {
#PrimaryGeneratedColumn('uuid')
#Field()
id: string;
#Column()
#Field()
name: string;
#Column()
#Field()
email: string;
#Column()
#Field()
password: string;
#Column({
type: 'enum',
enum: RoleEnum,
default: RoleEnum.Author,
})
#Field()
role: RoleEnum;
#OneToMany((type) => Post, post => post.user)
#Field(type=>Post)
posts: Post[];
#OneToMany((type) => Comment, comment => comment.author)
#Field(type=>Comment)
comments: Comment[];
}
user.resolver.ts
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/createuser.dto';
import { userInput } from './input/user.input';
import { User } from './user.entity';
#Resolver((of=>User))
export class UsersResolver {
constructor(private userService: UserService) {
}
#Mutation(()=> [CreateUserDto])
async createUser (#Args('data') data: userInput){
return this.userService.createUser(data)
}
}
user.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { User } from './user.entity';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/createuser.dto';
#Injectable()
export class UserService {
constructor (#InjectRepository(User) private readonly userRepository: Repository<User>) {}
async createUser (data: CreateUserDto): Promise<User> {
const user = new User()
user.name=data.name
user.email=data.email
user.role=data.role
user.password=data.password
await this.userRepository.save(user)
return user
}
}
createUser.dto.ts
import { Field, ObjectType } from 'type-graphql';
import { registerEnumType } from '#nestjs/graphql';
enum RoleEnum {
Admin = 'Admin',
Author = 'Author',
}
registerEnumType(RoleEnum, {
name: 'RoleEnum',
});
#ObjectType()
export class CreateUserDto {
#Field() name: string;
#Field() email: string;
#Field() password: string;
#Field(type => RoleEnum) role: RoleEnum;
}
user.input.ts
import { Field, InputType } from 'type-graphql';
import { registerEnumType } from '#nestjs/graphql';
enum RoleEnum {
Admin = 'Admin',
Author = 'Author',
}
registerEnumType(RoleEnum, {
name: 'RoleEnum',
});
#InputType()
export class userInput {
#Field() name: string;
#Field() email: string;
#Field() password: string;
#Field(type => RoleEnum) role: RoleEnum;
}
I noticed few possible causes for the error,
1. user.resolver.ts
#Mutation() decorator should return the type [User], not the type [CreateUserInput]
#Mutation(()=> [User]) // change here
async createUser (#Args('data') data: userInput){
return this.userService.createUser(data)
}
2. user.input.ts
If you encounter error : "Input" cannot be found in the registry,
add #ArgsType() decorator to the class userInput
#InputType()
#ArgsType() //add decorator here
export class userInput {
#Field() name: string;
#Field() email: string;
#Field() password: string;
#Field(type => RoleEnum) role: RoleEnum;
}
note: I used "#nestjs/graphql": "^7.10.3",
I'm trying to create a singleton class so I could avoid opening the same database again.
I've read about creating a class provider with a static variable so it could stay the same, but I've seen that each time I navigate to another component, the static variable content is lost.
Here's my code so far
couchbase.service.ts
import { Injectable } from "#angular/core";
import { Couchbase } from "nativescript-couchbase";
import * as connection from "tns-core-modules/connectivity";
import { isAndroid } from "platform";
#Injectable()
export class CouchbaseService {
private static database: any;
constructor() {
console.log("enter constructor")
}
public static init(){
if(!CouchbaseService.database) {
console.log("enter init")
CouchbaseService.database = new Couchbase("data");
}
return CouchbaseService.database;
}
public getDatabase() {
return CouchbaseService.database;
}
...
}
app.component.ts: I've read that calling init from this parent class would make the app keep it for the children (note that I also tried passing the CouchbaseService as a constructor parameter, changing the method init to non-static)
import { Component } from "#angular/core";
import * as Platform from "platform";
import { CouchbaseService } from './services/couchbase.service';
#Component({
selector: "ns-app",
templateUrl: "app.component.html",
})
export class AppComponent {
constructor() {
CouchbaseService.init();
}
}
In my app.module.ts file, I added CouchbaseService to the providers list.
import { NgModule, ErrorHandler, NO_ERRORS_SCHEMA } from "#angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptUIListViewModule } from "nativescript-ui-listview/angular";
import { NativeScriptUISideDrawerModule } from "nativescript-ui-sidedrawer/angular";
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { Http } from "#angular/http";
import { NativeScriptUIDataFormModule } from "nativescript-ui-dataform/angular";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { registerElement } from 'nativescript-angular/element-registry';
import { CardView } from 'nativescript-cardview';
import { AppRoutingModule } from "./app.routing";
import { AppComponent } from "./app.component";
import { CouchbaseService } from "./services/couchbase.service";
import { HomeComponent } from "./components/home/home.component";
registerElement('CardView', () => CardView);
registerElement("MapboxView", () => require("nativescript-mapbox").MapboxView);
registerElement("Fab", () => require("nativescript-floatingactionbutton").Fab);
#NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule,
NativeScriptUIListViewModule,
NativeScriptUISideDrawerModule,
AppRoutingModule,
NativeScriptUIDataFormModule,
NativeScriptFormsModule,
NativeScriptHttpModule
],
declarations: [
AppComponent,
HomeComponent
],
providers: [
CouchbaseService,
],
schemas: [
NO_ERRORS_SCHEMA
],
entryComponents: [
],
})
/*
Pass your application module to the bootstrapModule function located in main.ts to start your app
*/
export class AppModule { }
When I look at the logs in the terminal, seems like each time I navigate to another component the service is restarted, so it looses the value of database.
I'm using NativeScript 4 with Angular 5 and TypeScript 2.7.2.
This is not how should make use of a singleton service. Let's do this way:
Change your service like this:
import { Injectable } from "#angular/core";
import { Couchbase } from "nativescript-couchbase";
import * as connection from "tns-core-modules/connectivity";
import { isAndroid } from "platform";
#Injectable()
export class CouchbaseService {
private database: any;
constructor() {
console.log("enter constructor")
this.init();
}
private init(){
if(!this.database) {
console.log("enter init")
this.database = new Couchbase("data");
}
}
public getDatabase() {
return this.database;
}
...
}
In your AppModule [or module where you have your service] specify the service in #NgModule's provider array like this:
#NgModule({
declarations: [
AppComponent,
YourComponent
],
imports: [
BrowserModule
],
providers: [CouchbaseService],
bootstrap: [AppComponent]
})
export class AppModule { }
Now in your component use constructor dependency injection like this:
import { Component } from "#angular/core";
import * as Platform from "platform";
import { CouchbaseService } from './services/couchbase.service';
#Component({
selector: "ns-app",
templateUrl: "app.component.html",
})
export class AppComponent {
constructor(private service: CouchbaseService) {
console.log(this.service.getDatabase());
}
}
I am learning Spring boot with Angular, and I am trying to get my first app up and running reading the following blogs:
http://mydevgeek.com/angular-4-crud-application-with-spring-boot-rest-service-part-2/
https://dzone.com/articles/build-a-basic-crud-app-with-angular-50-and-spring
I manage to get the app working using spring boot on the 8080 port, and it works as expected. However, when I try to get it to work with Angular, I get the following error:
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
core.js:3675
ERROR
{…}
error: error
bubbles: false
cancelBubble: false
cancelable: false
composed: false
currentTarget: null
defaultPrevented: false
eventPhase: 0
explicitOriginalTarget: XMLHttpRequest
__zone_symbol__errorfalse: null
__zone_symbol__loadfalse: null
__zone_symbol__xhrListener: function scheduleTask/target[XHR_LISTENER]()
__zone_symbol__xhrSync: false
__zone_symbol__xhrTask: Object { runCount: 0, _state: "notScheduled", type: "macroTask", … }
__zone_symbol__xhrURL: "//localhost:8080/cool-cars"
mozAnon: false
mozSystem: false
readyState: 4
response: ""
responseText: ""
responseType: "text"
responseURL: ""
status: 0
statusText: ""
timeout: 0
upload: XMLHttpRequestUpload { }
withCredentials: false
__proto__: XMLHttpRequestPrototype { open: patchMethod/proto[name](), setRequestHeader: setRequestHeader(), send: patchMethod/proto[name](), … }
isTrusted: true
lengthComputable: false
loaded: 0
originalTarget: XMLHttpRequest
__zone_symbol__errorfalse: null
__zone_symbol__loadfalse: null
__zone_symbol__xhrListener: function scheduleTask/target[XHR_LISTENER]()
__zone_symbol__xhrSync: false
__zone_symbol__xhrTask: Object { runCount: 0, _state: "notScheduled", type: "macroTask", … }
__zone_symbol__xhrURL: "//localhost:8080/cool-cars"
mozAnon: false
mozSystem: false
readyState: 4
response: ""
responseText: ""
responseType: "text"
responseURL: ""
status: 0
statusText: ""
timeout: 0
upload: XMLHttpRequestUpload { }
withCredentials: false
__proto__: XMLHttpRequestPrototype { open: patchMethod/proto[name](), setRequestHeader: setRequestHeader(), send: patchMethod/proto[name](), … }
target: XMLHttpRequest { __zone_symbol__xhrSync: false, __zone_symbol__xhrURL: "//localhost:8080/cool-cars", readyState: 4, … }
timeStamp: 5395.150856236699
total: 0
type: "error"
__proto__: ProgressEventPrototype { lengthComputable: Getter, loaded: Getter, total: Getter, … }
headers: Object { normalizedNames: Map, lazyUpdate: null, headers: Map }
message: "Http failure response for (unknown url): 0 Unknown Error"
name: "HttpErrorResponse"
ok: false
status: 0
statusText: "Unknown Error"
url: null
__proto__: Object { constructor: HttpErrorResponse() }
When I look at the network tab, I see the following information:
Header:
Accept
application/json, text/plain, /
Accept-Encoding
gzip, deflate
Accept-Language
fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Connection
keep-alive
Host
localhost:8080
Request URL: http://localhost:8080/cool-cars
Request Method: GET
But there is no response. It seems the request times out.
The Chrome network tab shows this:
Request URL:http://localhost:8080/cool-cars
Referrer Policy:no-referrer-when-downgrade
Provisional headers are shown
Accept:application/json, text/plain, */*
Origin:http://localhost:4200
Referer:http://localhost:4200/
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36
My Java classes:
Car class:
import lombok.*;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Entity;
#Entity
#Getter #Setter
#NoArgsConstructor
#ToString #EqualsAndHashCode
public class Car {
#Id #GeneratedValue
private Long id;
private #NonNull String name;
}
Car repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.web.bind.annotation.CrossOrigin;
#RepositoryRestResource
#CrossOrigin(origins = "http://localhost:4200")
public interface CarRepository extends JpaRepository<Car, Long> {
}
Controller:
import java.util.Collection;
import java.util.stream.Collectors;
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/")
public class CoolCarController {
private CarRepository repository;
public CoolCarController(CarRepository repository) {
this.repository = repository;
}
#GetMapping("/cool-cars")
public Collection<Car> coolCars() {
return repository.findAll().stream()
.filter(this::isCool)
.collect(Collectors.toList());
}
private boolean isCool(Car car) {
return !car.getName().equals("AMC Gremlin") &&
!car.getName().equals("Triumph Stag") &&
!car.getName().equals("Ford Pinto") &&
!car.getName().equals("Yugo GV");
}
}
App class:
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
ApplicationRunner init(CarRepository repository) {
return args -> {
Stream.of("Ferrari", "Jaguar", "Porsche", "Lamborghini", "Bugatti",
"AMC Gremlin", "Triumph Stag", "Ford Pinto", "Yugo GV").forEach(name -> {
Car car = new Car();
car.setName(name);
repository.save(car);
});
repository.findAll().forEach(System.out::println);
};
}
}
As you can see, the CORS is enable with annotations.
In Angular, I have made the following changes.
car.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class CarService {
constructor(private http: HttpClient) { }
getAll(): Observable<any> {
return this.http.get('//localhost:8080/cool-cars');
}
}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { CarService } from './shared/car/car.service';
import { HttpClientModule } from '#angular/common/http';
import { AppComponent } from './app.component';
import {CarListComponent} from "./car-list/car-list.component";
#NgModule({
declarations: [
AppComponent,
CarListComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [CarService],
bootstrap: [AppComponent]
})
export class AppModule { }
car list component:
import { Component, OnInit } from '#angular/core';
import { CarService } from '../shared/car/car.service';
#Component({
selector: 'app-car-list',
templateUrl: './car-list.component.html',
styleUrls: ['./car-list.component.css']
})
export class CarListComponent implements OnInit {
cars: Array<any>;
constructor(private carService: CarService) { }
ngOnInit() {
this.carService.getAll().subscribe(data => {
this.cars = data;
});
}
}
car list component html
<h2>Car List</h2>
<div *ngFor="let car of cars">
{{car.name}}
</div>
I am using IntelliJ.
I have read a lot about this issue, including similar questions on this topic, but I did not see an answer that could resolve this issue (and that had the same empty response). Thanks a lot in advance for your help!
AIM: I send a http request from angular 2 ui to java server. While it is executing, server generates messages with progress status so I can update progress bar on ui.
I have 3 entities: AppComponent, AppService and WebsocketService.
Here is a simplified example of WebsocketService. It can subscribe to message topic and perform some actions on incoming each message.
export class WebsocketService {
private client: Client;
constructor() {
var service = this;
service.client = Stomp.client('ws://localhost:8080/stomp/websocket');
service.client.connect("user", "pass", function () {
service.client.subscribe("/progress", function (message) {
// some actions here
})
});
}
}
So, my question is: how to update AppComponent's property (field) value, which is binded to template, from AppService or even WebsocketService? Use a setter? Is it allright from the terms of MVC?
There is more than one way to do this but I would use a "Subject" stream.
Here is an example:
import {Injectable} from '#angular/core';
import {Http, Headers, RequestOptions, Response} from '#angular/http';
import {Observable} from 'rxjs/Rx';
import {Subject} from 'rxjs/Subject';
import {NextObserver} from 'rxjs/Observer';
export class WebsocketService {
public ProcessingMessages$: Observable<string>;
private client: Client;
private processingMessages: Subject<string>;
constructor() {
this.processingMessages = new Subject<string>();
this.ProcessingMessages$ = this.processingMessages.asObservable();
var service = this;
service.client = Stomp.client('ws://localhost:8080/stomp/websocket');
service.client.connect("user", "pass", function () {
service.client.subscribe("/progress", function (message) {
this.processingMessages.next(message);
})
});
}
}
// Sample Component
#Component({
selector: 'my-component',
template: 'WebsocketService Message: {{Message}}',
})
export class Sample Component implements OnInit {
public Message: string;
constructor(
private service: WebsocketService
) {
}
ngOnInit() {
this.service.ProcessingMessages$.subscribe((message: string) => {
this.Message = message;
});
}
}